Scala微服务架构 一

本文涉及的产品
云原生网关 MSE Higress,422元/月
注册配置 MSE Nacos/ZooKeeper,118元/月
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
简介: 因为公司的一个项目需求变动会非常频繁,同时改动在可控范围内,加上产品同学喜欢一些超前思维,我们的CTO决定提前开启八门,使用微服务架构。划重点微服务架构主要特点:==独立组件(自主开发升级)====开运一体(谁开发谁运维)====去中心化(语言,系统,数据,统统可以分开不一样)==一. 那么什么是微服务架构呢?引自 https://www.ibm.com/developerworks/community/blogs/“微服务”架构是近期软件应用领域非常热门的概念。

因为公司的一个项目需求变动会非常频繁,同时改动在可控范围内,加上产品同学喜欢一些超前思维,我们的CTO决定提前开启八门,使用微服务架构。

划重点

微服务架构主要特点:

  1. ==独立组件(自主开发升级)==
  2. ==开运一体(谁开发谁运维)==
  3. ==去中心化(语言,系统,数据,统统可以分开不一样)==

一. 那么什么是微服务架构呢?

引自 https://www.ibm.com/developerworks/community/blogs/

“微服务”架构是近期软件应用领域非常热门的概念。

微服务是一种架构风格,一个大型复杂软件应用由一个或多个微服务组成。系统中的各个微服务可被独立部署,各个微服务之间是松耦合的。每个微服务仅关注于完成一件任务并很好地完成该任务。在所有情况下,每个任务代表着一个小的业务能力。

特点

也就是说,微服务就是SOA的变种,在保证SOA业务分离能力的同时,减少每个业务粒度,同时去除了SOA中比较恼人的ESB.

用最精辟的话说就是==独立部署==,==快速迭代==.

通用特性

  1. 组件化,并可独立替换和升级
  2. 业务分离,每个服务团队运维一体化
  3. 服务产品化而非项目模式(其实和上面差不多,就是谁开发,谁运营)
  4. 智能端点与管道扁平化(Smartendpoints and dumb pipes):微服务架构主张将组件间通讯的相关业务逻辑/智能放在组件端点侧而非放在通讯组件中,通讯机制或组件应该尽量简单及松耦合。RESTful HTTP协议和仅提供消息路由功能的轻量级异步机制是微服务架构中最常用的通讯机制。
  5. “去中心化”治理(DecentralizedGovernance):整体式应用往往倾向于采用单一技术平台,微服务架构则鼓励使用合适的工具完成各自的任务,每个微服务可以考虑选用最佳工具完成(如不同的编程语言)。微服务的技术标准倾向于寻找其他开发者已成功验证解决类似问题的技术。
  6. “去中心化”数据管理(DecentralizedData Management):微服务架构倡导采用多样性持久化(PolyglotPersistence)的方法,让每个微服务管理其自有数据库,并允许不同微服务采用不同的数据持久化技术。
  7. 基础设施自动化(InfrastructureAutomation):云化及自动化部署等技术极大地降低了微服务构建、部署和运维的难度,通过应用持续集成和持续交付等方法有助于达到加速推出市场的目的。
  8. 故障处理设计(Designfor failure):微服务架构所带来的一个后果是必须考虑每个服务的失败容错机制。因此,微服务非常重视建立架构及业务相关指标的实时监控和日志机制。
  9. 演进式的设计(EvolutionaryDesign):如某应用是整体式应用,但逐渐朝微应用架构方向演进,整体式应用仍是核心,但新功能将使用应用所提供的API构建。再如在某微服务应用中,可替代性模块化设计的基本原则,在实施后发现某两个微服务经常必须同时更新,则这很可能意味着应将其合并为一个微服务。

自由组装

这个图片从资源利用率的角度,体现了微服务一个非常给力的优点
image

数据分离

同时,对应用组件封装的方式是整体架构与微服务架构的主要差异,微服务架构将相关联的业务逻辑及数据放在一起形成独立的边界,其目的是能在不影响其他应用组件(微服务)的情况下更快地交付并推出市场。
image

概念比较

在同一范畴内比较才有意义:

  • 微服务架构 vs. SOA – 两者都是架构风格范畴,但其关注领域与涉及范围不同。SOA更关注企业规模范围,微服务架构则更关注应用规模范围。
  • 微服务组件 vs. 服务组件 – 两者都是描述业务功能的具体实现,其区别在于粒度不同,此外还有在可管理性、灵活性上的差异。

概念混淆的不恰当比较

  • 微服务 vs. SOA – 不恰当的比较。微服务是组件范畴,而SOA是一种架构设计风格。因此应该比较的是微服务架构与SOA。
  • 微服务 vs. API – 不恰当的比较。 API是接口,是业务功能暴露的一种机制。微服务架构是用于实施业务功能的组件架构。因此直接比较它们是没有意义的。
  • 微服务 vs. 服务– 不恰当的比较。“服务”在不同的场景下有不同的含义,需要进一步澄清其描述的语境,是指服务实施、服务暴露、服务定义还是其他?微服务亦是如此,需要有特定语境才可判断比较是否有意义。

二. 前后对接协议 JsonApi

官网

※. 本期语法糖

※.1 Scala的Package Object

官网讲解https://www.scala-lang.org/docu/files/packageobjects/packageobjects.html
为啥使用Package Object而不是Object呢?
原因很简单,答案是==代码更干净==。如何做到的呢?
我copy了官网的例子,诸位请看:

// in file gardening/fruits/Fruit.scala
package gardening.fruits
case class Fruit(name: String, color: String)
object apple extends Fruit("Apple", "green")
object plum extends Fruit("Plum", "blue")
object banana extends Fruit("Banana", "yellow") 

上面我在/gardening/fruits/包下定义了几个Object

如果想使用直接使用上面定义的几个Object,可以这样

package com

object main {
    import gardening.fruits._
    val planted = List(apple, plum, banana)    
    def showFruit(fruit: Fruit) {
        println(fruit.name +"s are "+ fruit.color)
    }
}

但是,其实我们还有更简洁的调用方式,各位也已经猜到了,就是Package Object
只要在调用包的上一级/gardening/包下定义Package Object fruits,这样的写法就和class和objcet的伴生关系一样,我们可以直接访问伴生包中的变量,方法和隐式。

// in file gardening/fruits/package.scala
package gardening
package object fruits {
    val planted = List(apple, plum, banana)               
    def showFruit(fruit: Fruit) {
        println(fruit.name +"s are "+ fruit.color)
    }
}

这里要注意,思想不要局限,反过来也是可以的,比如在Package Object fruits中定义了一系列的object,在/gardening/包下的object可以直接使用。

※.2 Scala的自定义注解(Annotation)

本段摘自

※.2.1 Annotation (注解)的介绍

Scala中的注解语法与Java中类似。
标准库定义的注解相关内容在包scala.annotation中。

注解的基本语法为:

@注解名称(注解参数...)
val a = ???

与Java注解的用法类似,注解参数不是必须的,一个元素允许拥有多个注解。

※.2.2 Annotation的定义

==Scala中的自定义注解不是接口/特质,而是类.==
自定义注解需要从注解特质中继承,Scala中提供了两类注解特质:

  • scala.annotation.ClassfileAnnotation 由Java编译器生成注解
  • scala.annotation.StaticAnnotation 由Scala编译器生成注解

两类注解特质都继承自基类scala.annotation.Annotation.两类注解特质的基类相同,因此自定义注解类时允许同时混入两类注解特质。

  • 继承自ClassfileAnnotation基类的注解在使用时参数应以具名参数(named arguments)形式传入。
  • 继承自StaticAnnotation基类的注解无此限制。

定义注解类语法与普通类相同:

// 标记注解
class 注解名称 extends StaticAnnotation/ClassfileAnnotation

// 有参注解
class 注解名称(参数表...) extends StaticAnnotation/ClassfileAnnotation

※.2.3 Annotation的解析(表达式树)

通过反射机制获取注解信息,相关API位于scala.reflect.runtime.universe包路径下。

获取注解

  • 获取类的注解:
    1. 使用typeOf()方法,获取Type类型的类信息。typeOf[Test]: Type
    2. 通过Type.typeSymbol获取Symbol。type.typeSymbol: Symbol
    3. 通过Symbol.annotations获取List[Annotation](注解列表)。symbol.annotations.head: Annotation
    4. 根据注解列表的head获得语法树annotation.tree: Tree
  • 获取方法/成员字段的注解:
    1. 使用typeOf()方法,获取Type类型的类信息。typeOf[Test]: Type
    2. 通过Type.decls/decl()方法筛选出目标成员的Symbol。type.decl(TermName("ff ")): Symbol
    3. 通过Symbol.annotations获取List[Annotation](注解列表)。symbol.annotations.head: Annotation
    4. 根据注解列表的head获得语法树annotation.tree: Tree
  • 获取方法参数的注解:
    1. 使用typeOf()方法,获取Type类型的类信息。typeOf[Test]: Type
    2. 通过Type.decls/decl()方法筛选出目标方法的MethodSymbol。type.decl(TermName("ff ")): MethodSymbol
      通过MethodSymbol.paramLists方法获取目标方法的参数表(List[List[Symbol]]类型,方法柯里化可能会有多个参数表)。
    3. 通过Symbol.annotations获取List[Annotation](注解列表)。methodSymbol.annotations.head: Annotation

解析注解Dome
应使用Annotation.tree方法获取注解语法树,类型为scala.reflect.runtime.universe.Tree。

import scala.annotation.StaticAnnotation
import scala.reflect.runtime.universe._

class CustomAnnotation(name: String, num: Int) extends StaticAnnotation {
  override def toString = s"Annotation args: name -> $name, num -> $num"
}

@CustomAnnotation("Annotation for Class", 2333)
class Test {
  @CustomAnnotation("Annotation for Class", 6666)
  val ff = ""
}

object Main extends App {

  {
    // 获取类型注解
    val tpe: Type = typeOf[Test]
    val symbol: Symbol = tpe.typeSymbol //获取类型符号信息
    val annotation: Annotation = symbol.annotations.head
    val tree: Tree = annotation.tree //获取语法树

    // 解析注解语法树...
  }

  {
    // 获取成员字段注解
    val tpe: Type = typeOf[Test]
    val symbol: Symbol = tpe.decl(TermName("ff ")) //获取字段符号信息
    val annotation: Annotation = symbol.annotations.head
    val tree: Tree = annotation.tree

    // 解析注解语法树...
  }

}

※.3 Scala的implicit用法

平时我们比较常见的应该是使用implicit修饰val或者def,如果在函数的定义中,有如下函数签名

def divide(x: Int, y: Int)(implicit i2d: Int => Double): Double

则调用上述的divide方法时必须显示或隐式的传入一个类型为Int=>Double的函数映射.如:

divide(1, 2)(i => i.toDouble)

如果在当前名称空间中,正好有了这个Int => Double的函数映射,并且是隐式的,如:

implicit val test: Int => Double = i => i.toDouble

则函数调用的时候,则可以省略后面的隐式传入,编译器会自动使用当前名称空间的可用隐式,调用就变为:

divide(1, 2)

Implicit class

那么问题来了,如果implicit修饰的是class呢?
其实,我们可以认为带有这个关键字的类的主构造函数可用于隐式转换。
举个例子, 创建隐式类时,只需要在对应的类前加上implicit关键字。比如:

// 定义
object Helpers {
  implicit class IntWithTimes(x: Int) {
    def times[A](f: => A): Unit = {
      def loop(current: Int): Unit =
        if(current > 0) {
          f
          loop(current - 1)
        }
      loop(x)
    }
  }
}

// 调用
object main {
    import Helpers._
    5 times println("HI")
}

可以发现, 5是个Int型, 没有times方法,会自动查找名称空间,找到可以转换为IntWithTimes类型,并且带有times方法. 所以上面函数可以调用成功.

限制条件

  1. 使用隐式类时,类名必须在当前作用域内可见且无歧义,这一要求与隐式值等其他隐式类型转换方式类似。
  2. 只能在别的trait/类/对象内部定义。
  3. 构造函数只能携带一个非隐式参数。
  4. 在同一作用域内,不能有任何方法、成员或对象与隐式类同名。(也就说明case class 不可被implicit修饰,因为自动生成伴生类)

Implicit object

那么implicit修饰object又是什么鬼呢?

下面是个非常常见的例子:

object Math {
  trait NumberLike[T] {
    def plus(x: T, y: T): T
    def divide(x: T, y: Int): T
    def minus(x: T, y: T): T
  }
  object NumberLike {
    implicit object NumberLikeDouble extends NumberLike[Double] {
      def plus(x: Double, y: Double): Double = x + y
      def divide(x: Double, y: Int): Double = x / y
      def minus(x: Double, y: Double): Double = x - y
    }
    implicit object NumberLikeInt extends NumberLike[Int] {
      def plus(x: Int, y: Int): Int = x + y
      def divide(x: Int, y: Int): Int = x / y
      def minus(x: Int, y: Int): Int = x - y
    }
  }
}

实际上,看到的隐式object我们可以认为它就是

implicit val NumberLikeDouble: NumberLike[Double] = new NumberLike[Double] {
    ...
}
目录
相关文章
|
1月前
|
弹性计算 API 持续交付
后端服务架构的微服务化转型
本文旨在探讨后端服务从单体架构向微服务架构转型的过程,分析微服务架构的优势和面临的挑战。文章首先介绍单体架构的局限性,然后详细阐述微服务架构的核心概念及其在现代软件开发中的应用。通过对比两种架构,指出微服务化转型的必要性和实施策略。最后,讨论了微服务架构实施过程中可能遇到的问题及解决方案。
|
2月前
|
Cloud Native Devops 云计算
云计算的未来:云原生架构与微服务的革命####
【10月更文挑战第21天】 随着企业数字化转型的加速,云原生技术正迅速成为IT行业的新宠。本文深入探讨了云原生架构的核心理念、关键技术如容器化和微服务的优势,以及如何通过这些技术实现高效、灵活且可扩展的现代应用开发。我们将揭示云原生如何重塑软件开发流程,提升业务敏捷性,并探索其对企业IT架构的深远影响。 ####
57 3
|
2月前
|
Cloud Native 安全 数据安全/隐私保护
云原生架构下的微服务治理与挑战####
随着云计算技术的飞速发展,云原生架构以其高效、灵活、可扩展的特性成为现代企业IT架构的首选。本文聚焦于云原生环境下的微服务治理问题,探讨其在促进业务敏捷性的同时所面临的挑战及应对策略。通过分析微服务拆分、服务间通信、故障隔离与恢复等关键环节,本文旨在为读者提供一个关于如何在云原生环境中有效实施微服务治理的全面视角,助力企业在数字化转型的道路上稳健前行。 ####
|
1月前
|
Java 开发者 微服务
从单体到微服务:如何借助 Spring Cloud 实现架构转型
**Spring Cloud** 是一套基于 Spring 框架的**微服务架构解决方案**,它提供了一系列的工具和组件,帮助开发者快速构建分布式系统,尤其是微服务架构。
191 69
从单体到微服务:如何借助 Spring Cloud 实现架构转型
|
1月前
|
运维 监控 持续交付
微服务架构解析:跨越传统架构的技术革命
微服务架构(Microservices Architecture)是一种软件架构风格,它将一个大型的单体应用拆分为多个小而独立的服务,每个服务都可以独立开发、部署和扩展。
244 36
微服务架构解析:跨越传统架构的技术革命
|
1月前
|
设计模式 负载均衡 监控
探索微服务架构下的API网关设计
在微服务的大潮中,API网关如同一座桥梁,连接着服务的提供者与消费者。本文将深入探讨API网关的核心功能、设计原则及实现策略,旨在为读者揭示如何构建一个高效、可靠的API网关。通过分析API网关在微服务架构中的作用和挑战,我们将了解到,一个优秀的API网关不仅要处理服务路由、负载均衡、认证授权等基础问题,还需考虑如何提升系统的可扩展性、安全性和可维护性。文章最后将提供实用的代码示例,帮助读者更好地理解和应用API网关的设计概念。
73 8
|
2月前
|
Dubbo Java 应用服务中间件
服务架构的演进:从单体到微服务的探索之旅
随着企业业务的不断拓展和复杂度的提升,对软件系统架构的要求也日益严苛。传统的架构模式在应对现代业务场景时逐渐暴露出诸多局限性,于是服务架构开启了持续演变之路。从单体架构的简易便捷,到分布式架构的模块化解耦,再到微服务架构的精细化管理,企业对技术的选择变得至关重要,尤其是 Spring Cloud 和 Dubbo 等微服务技术的对比和应用,直接影响着项目的成败。 本篇文章会从服务架构的演进开始分析,探索从单体项目到微服务项目的演变过程。然后也会对目前常见的微服务技术进行对比,找到目前市面上所常用的技术给大家进行讲解。
70 1
服务架构的演进:从单体到微服务的探索之旅
|
2月前
|
负载均衡 Java 持续交付
深入解析微服务架构中的服务发现与负载均衡
深入解析微服务架构中的服务发现与负载均衡
104 7
|
2月前
|
消息中间件 运维 Kubernetes
后端架构演进:从单体到微服务####
本文将探讨后端架构的演变过程,重点分析从传统的单体架构向现代微服务架构的转变。通过实际案例和理论解析,揭示这一转变背后的技术驱动力、挑战及最佳实践。文章还将讨论在采用微服务架构时需考虑的关键因素,包括服务划分、通信机制、数据管理以及部署策略,旨在为读者提供一个全面的架构转型视角。 ####
48 1
|
2月前
|
弹性计算 运维 开发者
后端架构优化:微服务与容器化的协同进化
在现代软件开发中,后端架构的优化是提高系统性能和可维护性的关键。本文探讨了微服务架构与容器化技术如何相辅相成,共同推动后端系统的高效运行。通过分析两者的优势和挑战,我们提出了一系列最佳实践策略,旨在帮助开发者构建更加灵活、可扩展的后端服务。