精华内容
下载资源
问答
  • 微服务之间调用

    2021-03-21 13:46:19
    微服务调用特点 微服务调用和应用内调用不同点在于它是跨进程的,甚至是跨节点的,这意味着两点: 1、对外部有了依赖 2、如果是跨节点,就有了网络调用。我们知道网络都是不可靠的 如何做? 对外部有了依赖 微服务...

    微服务调用特点

    微服务调用和应用内调用不同点在于它是跨进程的,甚至是跨节点的,这意味着两点:
    1、对外部有了依赖
    2、如果是跨节点,就有了网络调用。我们知道网络都是不可靠的

    如何做?

    对外部有了依赖

    微服务架构设计中有一条重要的原则叫严出宽进,严出意思就是说你提供给其他服务的东西要尽可能的进行严格的校验。宽进就是你调用别人的接口要宽容,兼容各种情况。比如说你需要考虑别人的节点down了/api超时/api不可用等等因素。

    为了解决这个问题,我们必须要针对具体业务,分析依赖类型,是强依赖还是弱依赖,强依赖包装成自己的服务异常返回码/或者直接告诉前端调用不可用。弱依赖,catch所有异常,无论依赖方发生什么,不能影响我的接口返回。

    此外,我依赖的服务某段时间内接口错误率很高,调用方还在不停的发送请求,那么就会一直得到错误的结果,这时候这些请求其实是无效的,所以这时候需要客户端熔断,不再去调用服务方,给服务方恢复的时间,等过段时间再去重试,发现服务方可用时,再去调用。

    网络调用

    网络调用是耗时的,所以我们需要利用池化技术,复用连接,比如在单体应用中我们需要与数据库连接,会利用到数据库连接池来提高数据操作效率。那么微服务调用也是这么个道理,我们与其他服务建议http/tcp连接,也需要建立连接池。另外一个服务可能会依赖多个微服务,不能因为和某个服务之间的连接出了问题,影响到与其他服务的连接,所以需要做连接池隔离。

    服务间调用有可能失败,所以我们需要有重试机制,比如因为网络抖动引发的超时问题,我们可以通过重试提高API的可用性。 但是思考一下坏的情况,某段时间网络或者服务端真的有问题了。客户端超时时间设置的很大的话,客户端等待的时间就会很长,然后再加上重试机制,就会将客户端的连接占满,造成客户端相关API不可用。

    简单代码实现

    假设一个业务场景:
    咱们现在开发一个电商网站,要实现支付订单的功能,流程如下:
    1、创建一个订单之后,如果用户立刻支付了这个订单,我们需要将订单状态更新为“已支付”
    2、扣减相应的商品库存
    针对上述流程,我们需要有订单服务、库存服务。整个流程的大体思路如下:

    用户针对一个订单完成支付之后,就会去找订单服务,更新订单状态

    订单服务调用库存服务,完成相应功能

    至此,整个支付订单的业务流程结束

    下图这张图,清晰表明了各服务间的调用过程:

    在这里插入图片描述

    首先添加maven依赖

    <dependencies>
                <dependency>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-dependencies</artifactId>
                    <version>${spring-cloud.version}</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
                <dependency>
                    <groupId>com.alibaba.cloud</groupId>
                    <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                    <version>${spring-cloud-alibaba.version}</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
    
            </dependencies>
    

    配置文件
    application.properties配置如下

    server:
      port: 8071
    logging:
      level:
        root: info
    spring:
      datasource:
        url: jdbc:mysql://localhost:3306/demo?characterEncoding=utf-8&useSSL=false
        username: root
        password: lovo
        type: com.alibaba.druid.pool.DruidDataSource
      redis:
        database: 0
        host: localhost
        port: 6379
        jedis:
          pool:
            max-active: 8
            max-wait: -1
            max-idle: 8
            min-idle: 0
        timeout: 10000
      cloud:
        nacos:
          server-addr: localhost:8848
        sentinel:
          transport:
            port: 9999 #跟控制台交流的端口,随意指定一个未使用的端口即可
            dashboard: localhost:8858 # 指定控制台服务的地址
            client-ip: 192.168.65.1
      application:
        name: shop-commodity
    
    mybatis-plus:
      type-aliases-package: com.lovo.common.bean
      configuration:
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    

    启动类

    @SpringBootApplication
    @EnableDiscoveryClient
    @ComponentScan("com.lovo")
    public class CommodityApplication {
        public static void main(String[] args) {
            SpringApplication.run(CommodityApplication.class,args);
        }
    }
    

    然后编写服务之间调用的FeignClient代码
    feign调用实现

    @FeignClient(value="shop-commodity",configuration = FeignConfig.class)
    public interface ICommodityService {
        /**
         * 调用微服务的地址与请求类型
         */
        @GetMapping("/commodities/{id}")
        Result getCommodityById(@PathVariable("id") Long id);
        @PostMapping("/commoditySnapshots")
        Result addCommoditySnapshot(@RequestBody CommoditySnapshotBean commoditySnapshotBean);
    }
    
    
    展开全文
  • 下面是API Gateway示例图: 服务网关(API Gateway)不是为了解决微服务之间调用的紧耦合问题,它主要是为了简化客户端的工作。其实它还可以用来降低函数之间的耦合度。 有了API Gateway之后,一旦服务接口修改,你...

    点击上方“码农突围”,马上关注

    这里是码农充电第一站,回复“666”,获取一份专属大礼包
    真爱,请设置“星标”或点个“在看”
    

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。本文链接:https://blog.csdn.net/weixin_38748858/article/details/101062272

    在微服务架构中,需要调用很多服务才能完成一项功能。服务之间如何互相调用就变成微服务架构中的一个关键问题。

    服务调用有两种方式,一种是RPC方式,另一种是事件驱动(Event-driven)方式,也就是发消息方式。

    消息方式是松耦合方式,比紧耦合的RPC方式要优越,但RPC方式如果用在适合的场景也有它的一席之地。

    我们总在谈耦合,那么耦合到底意味着什么呢?

    耦合的种类:

    时间耦合: 客户端和服务端必须同时上线才能工作。发消息时,接受消息队列必须运行,但后台处理程序暂时不工作也不影响。

    容量耦合: 客户端和服务端的处理容量必须匹配。发消息时,如果后台处理能力不足也不要紧,消息队列会起到缓冲的作用。

    接口耦合: RPC调用有函数标签,而消息队列只是一个消息。例如买了商品之后要调用发货服务,如果是发消息,那么就只需发送一个商品被买消息。

    发送方式耦合: RPC是点对点方式,需要知道对方是谁,它的好处是能够传回返回值。消息既可以点对点,也可以用广播的方式,这样减少了耦合,但也使返回值比较困难。

    下面我们来逐一分析这些耦合的影响。第一,时间耦合,对于多数应用来讲,你希望能马上得到回答,因此即使使用消息队列,后台也需要一直工作。

    第二,容量耦合,如果你对回复有时间要求,那么消息队列的缓冲功能作用不大,因为你希望及时响应。

    真正需要的是自动伸缩(Auto-scaling),它能自动调整服务端处理能力去匹配请求数量。第三和第四,接口耦合和发送方式耦合,这两个确实是RPC方式的软肋。

    事件驱动(Event-Driven)方式

    Martin Fowler把事件驱动分成四种方式(What do you mean by “Event-Driven”),简化之后本质上只有两种方式。一种就是我们熟悉的的事件通知(Event Notification),另一种是事件溯源(Event Sourcing)。

    事件通知就是微服务之间不直接调用,而是通过发消息来进行合作。事件溯源有点像记账,它把所有的事件都记录下来,作为永久存储层,再在它的基础之上构建应用程序。

    实际上从应用的角度来讲,它们并不应该分属一类,它们的用途完全不同。事件通知是微服务的调用(或集成)方式,应该和RPC分在一起。事件溯源是一种存储数据的方式,应该和数据库分在一起。

    事件通知(Event Notification)方式

    让我们用具体的例子来看一下。在下面的例子中,有三个微服务,“Order Service”, “Customer Service” 和“Product Service”。

    先说读数据,假设要创建一个“Order”,在这个过程中需要读取“Customer”的数据和“Product”数据。

    如果用事件通知的方式就只能在“Order Service”本地也创建只读“Customer”和“Product”表,并把数据用消息的方式同步过来。

    再说写数据,如果在创建一个“Order”时需要创建一个新的“Customer”或要修改“Customer”的信息,那么可以在界面上跳转到用户创建页面,然后在“Customer Service”创建用户之后再发”用户已创建“的消息,“Order Service”接到消息,更新本地“Customer”表。

    这并不是一个很好的使用事件驱动的例子,因为事件驱动的优点就是不同的程序之间可以独立运行,没有绑定关系。但现在“Order Service”需要等待“Customer Service”创建完了之后才能继续运行,来完成整个创建“Order”的工作。主要是因为“Order”和“Customer”本身从逻辑上来讲就是紧耦合关系,没有“Customer”你是不能创建“Order”的。

    在这种紧耦合的情况下,也可以使用RPC。你可以建立一个更高层级的管理程序来管理这些微服务之间的调用,这样“Order Service”就不必直接调用“Customer Service”了。

    当然它从本质上来讲并没有解除耦合,只是把耦合转移到了上一层,但至少现在“order Service”和“Customer Service”可以互不影响了。之所以不能根除这种紧耦合关系是因为它们在业务上是紧耦合的。

    再举一个购物的例子。用户选好商品之后进行“Checkout”,生成“Order”,然后需要“payment”,再从“Inventory”取货,最后由“Shipment”发货,它们每一个都是微服务。这个例子用RPC方式和事件通知方式都可以完成。

    当用RPC方式时,由“Order”服务调用其他几个服务来完成整个功能。用事件通知方式时,“Checkout”服务完成之后发送“Order Placed”消息,“Payment”服务收到消息,接收用户付款,发送“Payment received”消息。

    “Inventory”服务收到消息,从仓库里取货,并发送“Goods fetched”消息。“Shipment”服务得到消息,发送货物,并发送“Goods shipped”消息。

    对这个例子来讲,使用事件驱动是一个不错的选择,因为每个服务发消息之后它不需要任何反馈,这个消息由下一个模块接收来完成下一步动作,时间上的要求也比上一个要宽松。用事件驱动的好处是降低了耦合度,坏处是你现在不能在程序里找到整个购物过程的步骤。

    如果一个业务逻辑有它自己相对固定的流程和步骤,那么使用RPC或业务流程管理(BPM)能够更方便地管理这些流程。在这种情况下选哪种方案呢?在我看来好处和坏处是大致相当的。从技术上来讲要选事件驱动,从业务上来讲要选RPC。不过现在越来越多的人采用事件通知作为微服务的集成方式,它似乎已经成了微服务之间的标椎调用方式。

    事件溯源(Event Sourcing)

    这是一种具有颠覆性质的的设计,它把系统中所有的数据都以事件(Event)的方式记录下来,它的持久存储叫Event Store, 一般是建立在数据库或消息队列(例如Kafka)基础之上,并提供了对事件进行操作的接口,例如事件的读写和查询。事件溯源是由领域驱动设计(Domain-Driven Design)提出来的。

    DDD中有一个很重要的概念,有界上下文(Bounded Context),可以用有界上下文来划分微服务,每个有界上下文都可以是一个微服务。下面是有界上下文的示例。下图中有两个服务“Sales”和“Support”。

    有界上下文的一个关键是如何处理共享成员, 在图中是“Customer”和“Product”。在不同的有界上下文中,共享成员的含义、用法以及他们的对象属性都会有些不同,DDD建议这些共享成员在各自的有界上下文中都分别建自己的类(包括数据库表),而不是共享。可以通过数据同步的手段来保持数据的一致性。下面还会详细讲解。

    事件溯源是微服务的一种存储方式,它是微服务的内部实现细节。因此你可以决定哪些微服务采用事件溯源方式,哪些不采用,而不必所有的服务都变成事件溯源的。通常整个应用程序只有一个Event Store, 不同的微服务都通过向Event Store发送和接受消息而互相通信。

    Event Store内部可以分成不同的stream(相当于消息队列中的Topic), 供不同的微服务中的领域实体(Domain Entity)使用。

    事件溯源的一个短板是数据查询,它有两种方式来解决。第一种是直接对stream进行查询,这只适合stream比较小并且查询比较简单的情况。

    查询复杂的话,就要采用第二种方式,那就是建立一个只读数据库,把需要的数据放在库中进行查询。数据库中的数据通过监听Event Store中相关的事件来更新。

    数据库存储方式只能保存当前状态,而事件溯源则存储了所有的历史状态,因而能根据需要回放到历史上任何一点的状态,具有很大优势。但它也不是一点问题都没有。

    第一,它的程序比较复杂,因为事件是一等公民,你必须把业务逻辑按照事件的方式整理出来,然后用事件来驱动程序。第二,如果你要想修改事件或事件的格式就比较麻烦,因为旧的事件已经存储在Event Store里了(事件就像日志,是只读的),没有办法再改。

    由于事件溯源和事件通知表面上看起来很像,不少人都搞不清楚它们的区别。事件通知只是微服务的集成方式,程序内部是不使用事件溯源的,内部实现仍然是传统的数据库方式。

    只有当要与其他微服务集成时才会发消息。而在事件溯源中,事件是一等公民,可以不要数据库,全部数据都是按照事件的方式存储的。

    虽然事件溯源的践行者有不同的意见,但有不少人都认为事件溯源不是微服务的集成方式,而是微服务的一种内部实现方式。因此,在一个系统中,可以某些微服务用事件溯源,另外一些微服务用数据库。

    当你要集成这些微服务时,你可以用事件通知的方式。注意现在有两种不同的事件需要区分开,一种是微服务的内部事件,是颗粒度比较细的,这种事件只发送到这个微服务的stream中,只被事件溯源使用。

    另一种是其他微服务也关心的,是颗粒度比较粗的,这种事件会放到另外一个或几个stream中,被多个微服务使用,是用来做服务之间集成的。这样做的好处是限制了事件的作用范围,减少了不相关事件对程序的干扰。详见"Domain Events vs. Event Sourcing"。

    事件溯源出现已经很长时间了,虽然热度一直在上升(尤其是这两年),但总的来说非常缓慢,谈论的人不少,但生产环境使用的不多。究其原因就是应为它对现在的体系结构颠覆太大,需要更改数据存储结构和程序的工作方式,还是有一定风险的。

    另外,微服务已经形成了一整套体系,从程序部署,服务发现与注册,到监控,服务韧性(Service Resilience),它们基本上都是针对RPC的,虽然也支持消息,但成熟度就差多了,因此有不少工作还是要自己来做。

    有意思的是Kafka一直在推动它作为事件驱动的工具,也取得了很大的成功。但它却没有得到事件溯源圈内的认可。

    多数事件溯源都使用一个叫evenstore的开源Event Store,或是基于某个数据库的Event Store,只有比较少的人用Kafka做Event Store。

    但如果用Kafka实现事件通知就一点问题都没有。总的来说,对大多数公司来讲事件溯源是有一定挑战的,应用时需要找到合适的场景。如果你要尝试的话,可以先拿一个微服务试水。

    虽然现在事件驱动还有些生涩,但从长远来讲,还是很看好它的。像其他全新的技术一样,事件溯源需要大规模的适用场景来推动。例如容器技术就是因为微服务的流行和推动,才走向主流。

    事件溯源以前的适用场景只限于记账和源代码库,局限性较大。区块链可能会成为它的下一个机遇,因为它用的也是事件溯源技术。

    另外AI今后会渗入到具体程序中,使程序具有学习功能。而RPC模式注定没有自适应功能。事件驱动本身就具有对事件进行反应的能力,这是自我学习的基础。因此,这项技术长远来讲定会大放异彩,但短期内(3-5年)大概不会成为主流。

    RPC方式

    RPC的方式就是远程函数调用,像RESTFul,gRPC, DUBBO 都是这种方式。它一般是同步的,可以马上得到结果。在实际中,大多数应用都要求立刻得到结果,这时同步方式更有优势,代码也更简单。

    服务网关(API Gateway)

    熟悉微服务的人可能都知道服务网关(API Gateway)。当UI需要调用很多微服务时,它需要了解每个服务的接口,这个工作量很大。

    于是就用服务网关创建了一个Facade,把几个微服务封装起来,这样UI就只调用服务网关就可以了,不需要去对付每一个微服务。下面是API Gateway示例图:

    服务网关(API Gateway)不是为了解决微服务之间调用的紧耦合问题,它主要是为了简化客户端的工作。其实它还可以用来降低函数之间的耦合度。

    有了API Gateway之后,一旦服务接口修改,你可能只需要修改API Gateway, 而不必修改每个调用这个函数的客户端,这样就减少了程序的耦合性。

    服务调用

    可以借鉴API Gateway的思路来减少RPC调用的耦合度,例如把多个微服务组织起来形成一个完整功能的服务组合,并对外提供统一的服务接口。这种想法跟上面的API Gateway有些相似,都是把服务集中起来提供粗颗粒(Coarse Granular)服务,而不是细颗粒的服务(Fine Granular)。

    但这样建立的服务组合可能只适合一个程序使用,没有多少共享价值。因此如果有合适的场景就采用,否侧也不必强求。虽然我们不能降低RPC服务之间的耦合度,却可以减少这种紧耦合带来的影响。

    降低紧耦合的影响

    什么是紧耦合的主要问题呢?就是客户端和服务端的升级不同步。服务端总是先升级,客户端可能有很多,如果要求它们同时升级是不现实的。它们有各自的部署时间表,一般都会选择在下一次部署时顺带升级。

    一般有两个办法可以解决这个问题:

    同时支持多个版本:这个工作量比较大,因此大多数公司都不会采用这种方式。

    服务端向后兼容:这是更通用的方式。例如你要加一个新功能或有些客户要求给原来的函数增加一个新的参数,但别的客户不需要这个参数。这时你只好新建一个函数,跟原来的功能差不多,只是多了一个参数。这样新旧客户的需求都能满足。它的好处是向后兼容(当然这取决于你使用的协议)。

    它的坏处是当以后新的客户来了,看到两个差不多的函数就糊涂了,不知道该用那个。而且时间越长越严重,你的服务端可能功能增加的不多,但相似的函数却越来越多,无法选择。

    它的解决办法就是使用一个支持向后兼容的RPC协议,现在最好的就是Protobuf gRPC,尤其是在向后兼容上。

    它给每个服务定义了一个接口,这个接口是与编程语言无关的中性接口,然后你可以用工具生成各个语言的实现代码,供不同语言使用。函数定义的变量都有编号,变量可以是可选类型的,这样就比较好地解决了函数兼容的问题。

    就用上面的例子,当你要增加一个可选参数时,你就定义一个新的可选变量。由于它是可选的,原来的客户端不需要提供这个参数,因此不需要修改程序。

    而新的客户端可以提供这个参数。你只要在服务端能同时处理这两种情况就行了。这样服务端并没有增加新的函数,但用户的新需求满足了,而且还是向后兼容的。

    微服务的数量有没有上限?

    总的来说微服务的数量不要太多,不然会有比较重的运维负担。有一点需要明确的是微服务的流行不是因为技术上的创新,而是为了满足管理上的需要。单体程序大了之后,各个模块的部署时间要求不同,对服务器的优化要求也不同,而且团队人数众多,很难协调管理。

    把程序拆分成微服务之后,每个团队负责几个服务,就容易管理了,而且每个团队也可以按照自己的节奏进行创新,但它给运维带来了巨大的麻烦。所以在微服务刚出来时,我一直觉得它是一个退步,弊大于利。但由于管理上的问题没有其他解决方案,只有硬着头皮上了。

    值得庆幸的是微服务带来的麻烦都是可解的。直到后来,微服务建立了全套的自动化体系,从程序集成到部署,从全链路跟踪到日志,以及服务检测,服务发现和注册,这样才把微服务的工作量降了下来。

    虽然微服务在技术上一无是处,但它的流行还是大大推动了容器技术,服务网格(Service Mesh)和全链路跟踪等新技术的发展。不过它本身在技术上还是没有发现任何优势。

    直到有一天,我意识到单体程序其实性能调试是很困难的(很难分离出瓶颈点),而微服务配置了全链路跟踪之后,能很快找到症结所在。看来微服务从技术来讲也不全是缺点,总算也有好的地方。但微服务的颗粒度不宜过细,否则工作量还是太大。

    一般规模的公司十几个或几十个微服务都是可以承受的,但如果有几百个甚至上千个,那么绝不是一般公司可以管理的。尽管现有的工具已经很齐全了,而且与微服务有关的整个流程也已经基本上全部自动化了,但它还是会增加很多工作。

    Martin Fowler几年以前建议先从单体程序开始(详见 MonolithFirst),然后再逐步把功能拆分出去,变成一个个的微服务。但是后来有人反对这个建议,他也有些松口了。

    如果单体程序不是太大,这是个好主意。可以用数据额库表的数量来衡量程序的大小,我见过大的单体程序有几百张表,这就太多了,很难管理。正常情况下,一个微服务可以有两、三张表到五、六张表,一般不超过十张表。但如果要减少微服务数量的话,可以把这个标准放宽到不要超过二十张表。

    用这个做为大致的指标来创建微程序,如果使用一段时间之后还是觉得太大了,那么再逐渐拆分。当然,按照这个标准建立的服务更像是服务组合,而不是单个的微服务。不过它会为你减少工作量。只要不影响业务部门的创新进度,这是一个不错的方案。

    到底应不应该选择微服务呢?如果单体程序已经没法管理了,那么你别无选择。如果没有管理上的问题,那么微服务带给你的只有问题和麻烦。其实,一般公司都没有太多选择,只能采用微服务,不过你可以选择建立比较少的微服务。如果还是没法决定,有一个折中的方案,“内部微服务设计”。

    内部微服务设计

    这种设计表面上看起来是一个单体程序,它只有一个源代码存储仓库,一个数据库,一个部署,但在程序内部可以按照微服务的思想来进行设计。它可以分成多个模块,每个模块是一个微服务,可以由不同的团队管理。

    用这张图做例子。这个图里的每个圆角方块大致是一个微服务,但我们可以把它作为一个单体程序来设计,内部有五个微服务。

    每个模块都有自己的数据库表,它们都在一个数据库中,但模块之间不能跨数据库访问(不要建立模块之间数据库表的外键)。

    “User”(在Conference Management模块中)是一个共享的类,但在不同的模块中的名字不同,含义和用法也不同,成员也不一样(例如,在“Customer Service”里叫“Customer”)。

    DDD(Domain-Driven Design)建议不要共享这个类,而是在每一个有界上下文(模块)中都建一个新类,并拥有新的名字。

    虽然它们的数据库中的数据应该大致相同,但DDD建议每一个有界上下文中都建一个新表,它们之间再进行数据同步。

    这个所谓的“内部微服务设计”其实就是DDD,但当时还没有微服务,因此外表看起来是单体程序,但内部已经是微服务的设计了。

    它的书在2003就出版了,当时就很有名。但它更偏重于业务逻辑的设计,践行起来也比较困难,因此大家谈论得很多,真正用的较少。

    直到十年之后,微服务出来之后,人们发现它其实内部就是微服务,而且微服务的设计需要用它的思想来指导,于是就又重新焕发了青春,而且这次更猛,已经到了每个谈论微服务的人都不得不谈论DDD的地步。不过一本软件书籍,在十年之后还能指导新技术的设计,非常令人钦佩。

    这样设计的好处是它是一个单体程序,省去了多个微服务带来的部署、运维的麻烦。但它内部是按微服务设计的,如果以后要拆分成微服务会比较容易。至于什么时候拆分不是一个技术问题。

    如果负责这个单体程序的各个团队之间不能在部署时间表,服务器优化等方面达成一致,那么就需要拆分了。

    当然你也要应对随之而来的各种运维麻烦。内部微服务设计是一个折中的方案,如果你想试水微服务,但又不愿意冒太大风险时,这是一个不错的选择。

    结论

    微服务之间的调用有两种方式,RPC和事件驱动。事件驱动是更好的方式,因为它是松耦合的。但如果业务逻辑是紧耦合的,RPC方式也是可行的(它的好处是代码更简单),而且你还可以通过选取合适的协议(Protobuf gRPC)来降低这种紧耦合带来的危害。

    由于事件溯源和事件通知的相似性,很多人把两者弄混了,但它们实际上是完全不同的东西。微服务的数量不宜太多,可以先创建比较大的微服务(更像是服务组合)。

    如果你还是不能确定是否采用微服务架构,可以先从“内部微服务设计”开始,再逐渐拆分。

    ---END---
    重磅!码农突围-技术交流群已成立
    扫码可添加码农突围助手,可申请加入码农突围大群和细分方向群,细分方向已涵盖:Java、Python、机器学习、大数据、人工智能等群。一定要备注:开发方向+地点+学校/公司+昵称(如Java开发+上海+拼夕夕+猴子),根据格式备注,可更快被通过且邀请进群
    ▲长按加群
    推荐阅读•  为什么要放弃 Lombok ?•  太肝了、最近5年183个Java面试问题列表及回答(值得收藏)•  为什么 30 岁的工程师容易跳槽?•  再见!  JSP !•  2020互联网大厂平均薪资出炉,你在哪个区间?•  一个HTTP请求的曲折经历...最近面试BAT,整理一份面试资料《Java面试BAT通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。获取方式:点“在看”,关注公众号并回复 BAT 领取,更多内容陆续奉上。如有收获,点个在看,诚挚感谢明天见(。・ω・。)ノ♡
    
    展开全文
  • 微服务之间的相互调用

    千次阅读 2019-12-25 10:51:27
    } } 然后编写服务之间调用的FeignClient代码 feign调用实现 @FeignClient(name = "com-kd-hello") public interface MyFeignClient{ /** * 调用微服务的地址与请求类型 */ @RequestMapping(value = "/hello", ...

    我使用的是@FeignClient(name="com-kd-xxx")

    首先添加maven依赖

    <dependencies>
    	<dependency>
    		<groupId>org.springframework.cloud</groupId>
    		<artifactId>spring-cloud-starter-eureka</artifactId>
    	</dependency>
    	<dependency>
    		<groupId>org.springframework.boot</groupId>
    		<artifactId>spring-boot-starter-test</artifactId>
    		<scope>test</scope>
    	</dependency>
    </dependencies>

    配置文件

    application.properties配置如下

    spring.application.name=com-kd-hello
    server.port=8889
    eureka.client.serviceUrl.defaultZone=http://localhost:8000/eureka/

    启动类

    @EnableFeignClients
    @SpringCloudApplication
    public class SpringCloudApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(SpringCloudApplication.class, args);
        }
    }

     

    然后编写服务之间调用的FeignClient代码

    feign调用实现

    @FeignClient(name = "com-kd-hello")
    public interface MyFeignClient{
    
        /**
         * 调用微服务的地址与请求类型
         */
        @RequestMapping(value = "/hello", method = RequestMethod.POST, headers = "Content-type=application/json")
        ResponseResult<List<ConsEntity>> queryConsList(@RequestBody ConsEntity cons);
    }

     

    使用方式

    @Autowired
    MyFeignClient myFeignClient;

    然后myFeignClient.queryConsList(cons)就可以使用了

    展开全文
  • 上篇文章 Spring Cloud中如何保证各个微服务之间调用的安全性 我们介绍了各个微服务之间调用认证的方式以及原理今天我们继续接着上篇文章来聊一聊如何能够在调用方实现token的自动设置以及刷新我们的认证token是放在...

    上篇文章 Spring Cloud中如何保证各个微服务之间调用的安全性 我们介绍了各个微服务之间调用认证的方式以及原理

    今天我们继续接着上篇文章来聊一聊如何能够在调用方实现token的自动设置以及刷新

    我们的认证token是放在请求头中的,相对于把token放在请求参数中更为友好,对业务接口无侵入性

    但是这种方式如果需要自己设置token就麻烦了,如果是参数的形式,那么在调用的时候就把获取的token当做参数传就可以了

    House getHouseInfo(Long id, String token);
    

    传参的方式不好的就是每个接口需要增加一个参数的定义

    /**
     * 获取房产信息
     * @param houseId 房产编号
     * @return 
     */
    @GetMapping("/{houseId}/{token}")
    public ResponseData hosueInfo(@PathVariable("houseId")Long houseId,@PathVariable("token")String token) {
    	return ResponseData.ok(houseService.getHouseInfo(houseId));
    }
    

    或者下面这种方式

    /**
     * 获取房产信息
     * @param houseId 房产编号
     * @return 
     */
    @GetMapping("/")
    public ResponseData hosueInfo(Long houseId,String token) {
    	return ResponseData.ok(houseService.getHouseInfo(houseId));
    }
    

    如果是PathVariable这种方式,参数是必传的,不然无法进入接口内,如果是RequestParam这种方式,方法中不定义token参数,我估计也是可以的,至少不会报错,反正我们是统一的去判断有无权限

    所以说我们的token放在请求头中,是非常友好的

    接下来我们说说使用的问题

    在调用接口的时候怎么往请求头中添加token呢?

    每次调用的地方都去添加token是不是太烦了?

    其实在Zuul中我们可以用过滤器来统一添加token,这个时候可以使用置前的过滤器pre

    **
     * 调用服务前添加认证请求头过滤器
     *
     * @author yinjihuan
     * @create 2017-11-07 16:06
     **/
    public class AuthHeaderFilter extends ZuulFilter {
    
        public AuthHeaderFilter() {
            super();
        }
    
        @Override
        public boolean shouldFilter() {
            return true;
        }
    
        @Override
        public String filterType() {
            return "pre";
        }
    
        @Override
        public int filterOrder() {
            return 0;
        }
    
        @Override
        public Object run() {
            RequestContext ctx = RequestContext.getCurrentContext();
            ctx.addZuulRequestHeader("Authorization", TokenScheduledTask.token);
            return null;
        }
    }
    

    这样在每个请求转发到具体的微服务之前,我们给它添加了token信息,这个token信息是我们从TokenScheduledTask获取的

    TokenScheduledTask是怎么获取token的呢?

    /**
     * 定时刷新token
     *
     * @author yinjihuan
     * @create 2017-11-09 15:39
     **/
    @Component
    public class TokenScheduledTask {
        private static Logger logger = LoggerFactory.getLogger(TokenScheduledTask.class);
    
        public final static long ONE_Minute = 60 * 1000 * 60 * 20;
    
        public static String token = "";
    
        @Autowired
        private AuthService authService;
    
        /**
         * 刷新Token
         */
        @Scheduled(fixedDelay = ONE_Minute)
        public void reloadApiToken() {
            token = authService.getToken();
            while (StringUtils.isBlank(token)) {
                try {
                    Thread.sleep(1000);
                    token = authService.getToken();
                } catch (InterruptedException e) {
                    logger.error("", e);
                }
            }
        }
    
    }
    
    

    原来是一个定时任务,通过调用认证的方法来获取认证好的token

    ##为什么要做成定时的呢

    如果按照一般的做法那就是请求之后都去获取一次token, 这种方式是最不好的,性能太差

    稍微好点那就是在获取的地方加上缓存,貌似不错,但是有个问题是在并发的时候会存在N个请求去获取token,这边需要控制下

    定时的就不存在上面的问题了,但是一定要确保定时任务的正常

    我这边一个token的失效时间为24小时,所以我这边刷新的间隔是20小时,也就是说在token还没过期之前,我会自动刷新成最新的,这样就不会出现token过期的问题了

    while循环是为了确保token能够正确的刷新成功

    同时这个任务是在项目启动之后立马去刷新token的,这样就能确保刚过来的请求不会受到影响

    具体代码可以参考我的github:

    https://github.com/yinjihuan/spring-cloud

    欢迎加入我的知识星球,一起交流技术,免费学习猿天地的课程(http://cxytiandi.com/course)

    PS:目前星球中正在星主的带领下组队学习Sentinel,等你哦!

    微信扫码加入猿天地知识星球

    猿天地

    展开全文
  • SpringBoot微服务调用解决方案

    万次阅读 2019-04-19 16:05:21
    SpringBoot微服务调用解决方案方式一:中间服务方式二:互相调用方式三:两者组合使用问题 方式一:中间服务 优势: 将服务间的耦合度降到最低 ; 降低服务复杂度,各服务不需要变动; 缺点: 增加了服务数量,...
  • 当然实际情况肯定复杂的多,如果一个接口内部存在不相互依赖的耗时调用的话,那么我们可以做这样的合并,响应时间上的减少还是非常明显的。整个接口的响应时间取决于最长的那个内部接口。 那么我们来看看在 Java ...
  • 微服务中经常会采用,技术上选择事件驱动,业务上讲是RPC模式。事件通知作为微服务的集成方式,应用越来越广。 一、RPC方式(紧耦合): 点对点的远程函数标签模式,实时返回值,常见RESTFul,gRPC,Dubbo都是这种...
  • 今天继续SpringCloud微服务项目实战系列文章,前面的文章已经说了微服务的服务注册与发现,现在接着上一篇的话题,说说微服务之间调用。在实战之前,我们还是先掌握下基础理论等知识,继续以面试题的形式展开。 ...
  • 微服务架构相比单体架构,服务的调用从同一台机器内部的本地调用变成了不同机器之间的远程方法调用,但是这个过程也引入了两个不确定的因素: 调用的执行是在服务提供者一端,即使服务消费者本身是正常的,服务提供...
  • 》深入地阐述了微服务能够带来的利益,但是作为一项新兴的技术,总难免会遇到这样或那样的难题,今天就来一起看看微服务的难点之一:服务调用解决方案。微服务有很多难点,比如分布式系统:本文作者与红帽的顶级...
  • 如何解决微服务事务难题

    千次阅读 2018-06-21 20:36:23
    微服务架构下的应用是由一组松耦合的相互协调的服务所组成。这些服务内部通常使用独立的数据库来维护状态,服务与服务之间是通过轻量级的通讯协议进行交互的。如何协调这些服务之间的...
  • 上一篇我们将基础的微服务项目搭建完成,这一篇我们主要来看一下,服务之间如何去调用。 一、订单模块 (pom、application.yml 自己去配置啊,配置文件上一讲是有的) 首先我们去创建一个接口,创建订单的接口。 ...
  • 循环次数是,12个线程同时发送请求,每个线程发送2次,第二次发送时要在第一次返回之后再发送。 Ramp-Up时间,是几秒内把线程启动,例如设置成36 ,则36秒启动12个线程,每3秒启动一个。 1.熔断线程能极大减轻业务...
  • 微服务调用Ribbon负载均衡、Feign的使用1、微服务调用Ribbon2、Ribbon负载均衡3、Feign简介及应用 微服务调用Ribbon负载均衡、Feign的使用) 1、微服务调用Ribbon 一、简介 Ribbon是一个负载均衡组件,具有丰富的...
  • 此时不会产生任何冲突,谁开发feature有谁负责rebase,那么会产生一个非常好的良性循环,因为开发者对自己的代码非常熟悉,并且master的代码绝对是都要的。所以rebase出错的可能性降低了非常多,然后合并请求不存在...
  • 简单来说,就是如果订单服务对库存服务发起10次请求,那就先让你请求第1台机器、然后是第2台机器、第3台机器、第4台机器、第5台机器,接着再来—个循环,第1台机器、第2台机器。。。 以此类推 。 另外,Ribbon是...
  • RPC与Http远程调用 1、RPC :Remote Produce call ,自定义数据格式,基于tcp通讯,速度快效率高,早期的webservice 与dubbo . 2、http网络传输协议,基于tcp,规定了传输协议格式,目前服务通信,客户端浏览器都采用...
  • 程序有Bug:代码循环调用的逻辑问题,资源未释放引起的内存泄漏等问题; 硬件故障:比如宕机,机房断电,光纤被挖断等。 线程同步等待:系统间经常采用同步服务调用模式,核心服务和非核心服务共用...
  • @JsonBackReference // 解决循环调用的问题 @ManyToMany @JoinTable(name = "t_sys_user_role", joinColumns = { @JoinColumn(name = "roleId")}, inverseJoinColumns = { @JoinColumn(name = "uid") }) ...
  • 公司是采用微服务架构,服务即按照业务方向竖向拆分,同时同一个业务方向还按照业务层次横向拆分。其中有三个服务之间的关系如下图: A-service-1服务是A业务团队的一个比较基础的底层服务。基于springboot的2.1.5...
  • 微服务之openfeign

    2020-09-05 00:43:01
    原来微服务之间负载均衡 调用需要通过 restTemplate + Ribbon 实现.feign 实现了创建一个被调用方 接口的同样格式的接口,再加上feign的注解就可以实现微服务之间调用.类似于 mybatis 的 @mapper 注解 feign使用在...
  • 微服务

    2020-04-14 00:20:15
    要理解微服务,首先要先理解不是微服务的那些。通常跟微服务相对的是单体应用,即将所有功能都打包成在一个独立单元的应用程序。从单体应用到微服务并不是一蹴而就的,这是一个逐渐演变的过程。...
  • spring boot / cloud (十四) 微服务间远程服务调用的认证和鉴权的思考和设计,以及restFul风格的url匹配拦截方法 前言 本篇接着<spring boot / cloud (八) 使用RestTemplate来构建远程调用服务>这篇博客来继续...
  • 微服务中,rest服务互相调用是很普遍的,我们该如何优雅地调用,其实在Spring框架使用RestTemplate类可以优雅地进行rest服务互相调用,它简化了与http服务的通信方式,统一了RESTful的标准,封装了http链接,操作...
  • Ribbon 负载均衡服务调用8.1 概述1. Ribbon 是什么2. 官网资料3. 能干什么3.1 LB(负载均衡)8.2 Ribbon负载均衡演示1.架构说明2. pom3. RestTemplate 的使用8.3 Ribbon核心组件IRule如何替换?8.4 Ribbon负载均衡...
  • 微服务主要的优势: 1、降低复杂度 将原来偶合在一起的复杂业务拆分为单个服务,规避了原本复杂度无止境的积累。每一个微服务专注于单一功能,并通过定义良好的接口清晰表述服务边界。每个服务开发者只专注服务本身...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 15,531
精华内容 6,212
关键字:

如何解决微服务之间循环调用