akka-typed(4) – EventSourcedBehavior in action

  前面提到過,akka-typed中較重要的改變是加入了EventSourcedBehavior。也就是說增加了一種專門負責EventSource模式的actor, 最終和其它種類的actor一道可以完美實現CQRS。新的actor,我還是把它稱為persistentActor,還是一種能維護和維持運行狀態的actor。即,actor內部狀態可以存放在數據庫里,然後通過一組功能函數來提供對狀態的處理轉變,即持續化處理persistence。當然作為一種具備EventSourcedBehavior的actor, 普遍應有的actor屬性、方法、消息處理協議、監管什麼的都還必須存在。在這篇討論里我們就通過案例和源碼來說明一下EventSourcedBehavior是如何維護內部狀態及作為一種actor又應該怎麼去使用它。

我們把上一篇討論里購物車的例子拿來用,再增加一些消息回復response機制,主要是彙報購物車狀態:

object ItemInfo { case class Item(name: String, price: Double) } object MyCart { import ItemInfo._ sealed trait Command sealed trait Event extends CborSerializable sealed trait Response //commands
  case class AddItem(item: Item) extends Command case object PayCart extends Command case class CountItems(replyTo: ActorRef[Response]) extends Command //event
  case class ItemAdded(item: Item) extends Event case object CartPaid extends Event //state
  case class CartLoad(load: List[Item] = Nil) //response
  case class PickedItems(items: List[Item]) extends Response case object CartEmpty extends Response val commandHandler: (CartLoad, Command) => Effect[Event,CartLoad] = { (state, cmd) => cmd match { case AddItem(item) => Effect.persist(ItemAdded(item)) case PayCart => Effect.persist(CartPaid) case CountItems(replyTo) => Effect.none.thenRun { cart => cart.load match { case Nil => replyTo ! CartEmpty case listOfItems => replyTo ! PickedItems(listOfItems) } } } } val eventHandler: (CartLoad,Event) => CartLoad = { (state,evt) => evt match { case ItemAdded(item) => state.copy(load = item :: state.load) case CartPaid => state.copy(load = Nil) } } def apply(): Behavior[Command] = EventSourcedBehavior[Command,Event,CartLoad]( persistenceId = PersistenceId("10","1013"), emptyState = CartLoad(), commandHandler = commandHandler, eventHandler = eventHandler ) } object Shopper { import ItemInfo._ sealed trait Command extends CborSerializable case class GetItem(item: Item) extends Command case object Settle extends Command case object GetCount extends Command case class WrappedResponse(res: MyCart.Response) extends Command def apply(): Behavior[Command] = Behaviors.setup[Command] { ctx => val shoppingCart = ctx.spawn(MyCart(), "shopping-cart") val cartRef: ActorRef[MyCart.Response] = ctx.messageAdapter(WrappedResponse) Behaviors.receiveMessage { msg => msg match { case GetItem(item) => shoppingCart ! MyCart.AddItem(item) case Settle => shoppingCart ! MyCart.PayCart case GetCount => shoppingCart ! MyCart.CountItems(cartRef) case WrappedResponse(res) => res match { case MyCart.PickedItems(items) => ctx.log.info("**************Current Items in Cart: {}*************", items) case MyCart.CartEmpty => ctx.log.info("**************shopping cart is empty!***************") } } Behaviors.same } } } object ShoppingCart extends App { import ItemInfo._ val shopper = ActorSystem(Shopper(),"shopper") shopper ! Shopper.GetItem(Item("banana",11.20)) shopper ! Shopper.GetItem(Item("watermelon",4.70)) shopper ! Shopper.GetCount shopper ! Shopper.Settle shopper ! Shopper.GetCount scala.io.StdIn.readLine() shopper.terminate() }

實際上EventSourcedBehavior里還嵌入了回復機制,完成一項Command處理后必須回復指令方,否則程序無法通過編譯。如下:

private def withdraw(acc: OpenedAccount, cmd: Withdraw): ReplyEffect[Event, Account] = { if (acc.canWithdraw(cmd.amount)) Effect.persist(Withdrawn(cmd.amount)).thenReply(cmd.replyTo)(_ => Confirmed) else Effect.reply(cmd.replyTo)(Rejected(s"Insufficient balance ${acc.balance} to be able to withdraw ${cmd.amount}")) }

不過這個回復機制是一種副作用。即,串連在Effect產生之後立即實施。這個動作是在eventHandler之前。在這個時段無法回復最新的狀態。

說到side-effect, 如Effect.persist().thenRun(produceSideEffect): 當成功持續化event后可以安心進行一些其它的操作。例如,當影響庫存數的event被persist后可以馬上從賬上扣減庫存。

在上面這個ShoppingCart例子里我們沒有發現狀態轉換代碼如Behaviors.same。這隻能是EventSourcedBehavior屬於更高層次的Behavior,狀態轉換已經嵌入在eventHandler里了,還記着這個函數的款式吧  (State,Event) => State, 這個State就是狀態了。

Events persist在journal里,如果persist操作中journal出現異常,EventSourcedBehavior自備了安全監管策略,如下:

  def apply(): Behavior[Command] = EventSourcedBehavior[Command,Event,CartLoad]( persistenceId = PersistenceId("10","1013"), emptyState = CartLoad(), commandHandler = commandHandler, eventHandler = eventHandler ).onPersistFailure( SupervisorStrategy .restartWithBackoff(minBackoff = 10.seconds, maxBackoff = 60.seconds, randomFactor = 0.1) .withMaxRestarts(3) .withResetBackoffAfter(10.seconds))

值得注意的是:這個策略只適用於onPersistFailure(),從外部用Behaviors.supervisor()包嵌是無法實現處理PersistFailure效果的。但整個actor還是需要一種Backoff策略,因為在EventSourcedBehavior內部commandHandler,eventHandler里可能也會涉及一些數據庫操作。在操作失敗后需要某種Backoff重啟策略。那麼我們可以為actor增加監控策略如下:

  def apply(): Behavior[Command] = Behaviors.supervise( Behaviors.setup { ctx => EventSourcedBehavior[Command, Event, CartLoad]( persistenceId = PersistenceId("10", "1013"), emptyState = CartLoad(), commandHandler = commandHandler, eventHandler = eventHandler ).onPersistFailure( SupervisorStrategy .restartWithBackoff(minBackoff = 10.seconds, maxBackoff = 60.seconds, randomFactor = 0.1) .withMaxRestarts(3) .withResetBackoffAfter(10.seconds)) } ).onFailure( SupervisorStrategy .restartWithBackoff(minBackoff = 10.seconds, maxBackoff = 60.seconds, randomFactor = 0.1) .withMaxRestarts(3) .withResetBackoffAfter(10.seconds) )

現在這個MyCart可以說已經是個安全、強韌性的actor了。

既然是一種persistentActor,那麼持久化的管理應該也算是核心功能了。EventSourcedBehavior通過接收信號提供了對持久化過程監控功能,如:

 def apply(): Behavior[Command] = Behaviors.supervise( Behaviors.setup[Command] { ctx => EventSourcedBehavior[Command, Event, CartLoad]( persistenceId = PersistenceId("10", "1013"), emptyState = CartLoad(), commandHandler = commandHandler, eventHandler = eventHandler ).onPersistFailure( SupervisorStrategy .restartWithBackoff(minBackoff = 10.seconds, maxBackoff = 60.seconds, randomFactor = 0.1) .withMaxRestarts(3) .withResetBackoffAfter(10.seconds) ).receiveSignal { case (state, RecoveryCompleted) => ctx.log.info("**************Recovery Completed with state: {}***************",state) case (state, SnapshotCompleted(meta))  => ctx.log.info("**************Snapshot Completed with state: {},id({},{})***************",state,meta.persistenceId, meta.sequenceNr) case (state,RecoveryFailed(err)) => ctx.log.error("recovery failed with: {}",err.getMessage) case (state,SnapshotFailed(meta,err)) => ctx.log.error("snapshoting failed with: {}",err.getMessage) } } ).onFailure( SupervisorStrategy .restartWithBackoff(minBackoff = 10.seconds, maxBackoff = 60.seconds, randomFactor = 0.1) .withMaxRestarts(3) .withResetBackoffAfter(10.seconds) )

EventSourcedBehavior.receiveSignal是個偏函數:

  def receiveSignal(signalHandler: PartialFunction[(State, Signal), Unit]): EventSourcedBehavior[Command, Event, State]

下面是一個EventSourcedBehavior Signal 清單:

sealed trait EventSourcedSignal extends Signal @DoNotInherit sealed abstract class RecoveryCompleted extends EventSourcedSignal case object RecoveryCompleted extends RecoveryCompleted { def instance: RecoveryCompleted = this } final case class RecoveryFailed(failure: Throwable) extends EventSourcedSignal { def getFailure(): Throwable = failure } final case class SnapshotCompleted(metadata: SnapshotMetadata) extends EventSourcedSignal { def getSnapshotMetadata(): SnapshotMetadata = metadata } final case class SnapshotFailed(metadata: SnapshotMetadata, failure: Throwable) extends EventSourcedSignal { def getFailure(): Throwable = failure def getSnapshotMetadata(): SnapshotMetadata = metadata } object SnapshotMetadata { /** * @param persistenceId id of persistent actor from which the snapshot was taken. * @param sequenceNr sequence number at which the snapshot was taken. * @param timestamp time at which the snapshot was saved, defaults to 0 when unknown. * in milliseconds from the epoch of 1970-01-01T00:00:00Z. */ def apply(persistenceId: String, sequenceNr: Long, timestamp: Long): SnapshotMetadata =
    new SnapshotMetadata(persistenceId, sequenceNr, timestamp) } /** * Snapshot metadata. * * @param persistenceId id of persistent actor from which the snapshot was taken. * @param sequenceNr sequence number at which the snapshot was taken. * @param timestamp time at which the snapshot was saved, defaults to 0 when unknown. * in milliseconds from the epoch of 1970-01-01T00:00:00Z. */ final class SnapshotMetadata(val persistenceId: String, val sequenceNr: Long, val timestamp: Long) { override def toString: String = s"SnapshotMetadata($persistenceId,$sequenceNr,$timestamp)" } final case class DeleteSnapshotsCompleted(target: DeletionTarget) extends EventSourcedSignal { def getTarget(): DeletionTarget = target } final case class DeleteSnapshotsFailed(target: DeletionTarget, failure: Throwable) extends EventSourcedSignal { def getFailure(): Throwable = failure def getTarget(): DeletionTarget = target } final case class DeleteEventsCompleted(toSequenceNr: Long) extends EventSourcedSignal { def getToSequenceNr(): Long = toSequenceNr } final case class DeleteEventsFailed(toSequenceNr: Long, failure: Throwable) extends EventSourcedSignal { def getFailure(): Throwable = failure def getToSequenceNr(): Long = toSequenceNr }

當然,EventSourcedBehavior之所以能具備自我修復能力其中一項是因為它有對持久化的事件重演機制。如果每次啟動都需要對所有歷史事件進行重演的話會很不現實。必須用snapshot來濃縮歷史事件:

  def apply(): Behavior[Command] = Behaviors.supervise( Behaviors.setup[Command] { ctx => EventSourcedBehavior[Command, Event, CartLoad]( persistenceId = PersistenceId("10", "1013"), emptyState = CartLoad(), commandHandler = commandHandler, eventHandler = eventHandler ).onPersistFailure( SupervisorStrategy .restartWithBackoff(minBackoff = 10.seconds, maxBackoff = 60.seconds, randomFactor = 0.1) .withMaxRestarts(3) .withResetBackoffAfter(10.seconds) ).receiveSignal { case (state, RecoveryCompleted) => ctx.log.info("**************Recovery Completed with state: {}***************",state) case (state, SnapshotCompleted(meta))  => ctx.log.info("**************Snapshot Completed with state: {},id({},{})***************",state,meta.persistenceId, meta.sequenceNr) case (state,RecoveryFailed(err)) => ctx.log.error("recovery failed with: {}",err.getMessage) case (state,SnapshotFailed(meta,err)) => ctx.log.error("snapshoting failed with: {}",err.getMessage) }.snapshotWhen { case (state,CartPaid,seqnum) => ctx.log.info("*****************snapshot taken at: {} with state: {}",seqnum,state) true
          case (state,event,seqnum) => false }.withRetention(RetentionCriteria.snapshotEvery(numberOfEvents = 100, keepNSnapshots = 2)) } ).onFailure( SupervisorStrategy .restartWithBackoff(minBackoff = 10.seconds, maxBackoff = 60.seconds, randomFactor = 0.1) .withMaxRestarts(3) .withResetBackoffAfter(10.seconds) )

下面是本次示範的源碼:

build.sbt

name := "learn-akka-typed"

version := "0.1"

scalaVersion := "2.13.1"
scalacOptions in Compile ++= Seq("-deprecation", "-feature", "-unchecked", "-Xlog-reflective-calls", "-Xlint")
javacOptions in Compile ++= Seq("-Xlint:unchecked", "-Xlint:deprecation")

val AkkaVersion = "2.6.5"
val AkkaPersistenceCassandraVersion = "1.0.0"


libraryDependencies ++= Seq(
  "com.typesafe.akka" %% "akka-cluster-sharding-typed" % AkkaVersion,
  "com.typesafe.akka" %% "akka-persistence-typed" % AkkaVersion,
  "com.typesafe.akka" %% "akka-persistence-query" % AkkaVersion,
  "com.typesafe.akka" %% "akka-serialization-jackson" % AkkaVersion,
  "com.typesafe.akka" %% "akka-persistence-cassandra" % AkkaPersistenceCassandraVersion,
  "com.typesafe.akka" %% "akka-slf4j" % AkkaVersion,
  "ch.qos.logback"     % "logback-classic"             % "1.2.3"
)

application.conf

akka.actor.allow-java-serialization = on
akka {
  loglevel = DEBUG
  actor {
    serialization-bindings {
      "com.learn.akka.CborSerializable" = jackson-cbor
    }
  }
  # use Cassandra to store both snapshots and the events of the persistent actors
  persistence {
    journal.plugin = "akka.persistence.cassandra.journal"
    snapshot-store.plugin = "akka.persistence.cassandra.snapshot"
  }

}
akka.persistence.cassandra {
  # don't use autocreate in production
  journal.keyspace = "poc"
  journal.keyspace-autocreate = on
  journal.tables-autocreate = on
  snapshot.keyspace = "poc_snapshot"
  snapshot.keyspace-autocreate = on
  snapshot.tables-autocreate = on
}

datastax-java-driver {
  basic.contact-points = ["192.168.11.189:9042"]
  basic.load-balancing-policy.local-datacenter = "datacenter1"
}

ShoppingCart.scala

package com.learn.akka

import akka.actor.typed._
import akka.persistence.typed._
import akka.actor.typed.scaladsl.Behaviors
import akka.persistence.typed.scaladsl._
import scala.concurrent.duration._

object ItemInfo {
  case class Item(name: String, price: Double)
}

object MyCart {
 import ItemInfo._

  sealed trait Command
  sealed trait Event extends CborSerializable
  sealed trait Response

  //commands
  case class AddItem(item: Item) extends Command
  case object PayCart extends Command
  case class CountItems(replyTo: ActorRef[Response]) extends Command

  //event
  case class ItemAdded(item: Item) extends Event
  case object CartPaid extends Event

  //state
  case class CartLoad(load: List[Item] = Nil)

  //response
  case class PickedItems(items: List[Item]) extends Response
  case object CartEmpty extends Response

  val commandHandler: (CartLoad, Command) => Effect[Event,CartLoad] = { (state, cmd) =>
    cmd match {
      case AddItem(item) =>
        Effect.persist(ItemAdded(item))
      case PayCart =>
        Effect.persist(CartPaid)
      case CountItems(replyTo) =>
        Effect.none.thenRun { cart =>
          cart.load match {
            case Nil =>
              replyTo ! CartEmpty
            case listOfItems =>
              replyTo ! PickedItems(listOfItems)
          }
        }
    }
  }

  val eventHandler: (CartLoad,Event) => CartLoad = { (state,evt) =>
    evt match {
      case ItemAdded(item) =>
         state.copy(load = item :: state.load)
      case CartPaid =>
        state.copy(load = Nil)
    }
  }

  def apply(): Behavior[Command] =
    Behaviors.supervise(
      Behaviors.setup[Command] { ctx =>
        EventSourcedBehavior[Command, Event, CartLoad](
          persistenceId = PersistenceId("10", "1013"),
          emptyState = CartLoad(),
          commandHandler = commandHandler,
          eventHandler = eventHandler
        ).onPersistFailure(
          SupervisorStrategy
            .restartWithBackoff(minBackoff = 10.seconds, maxBackoff = 60.seconds, randomFactor = 0.1)
            .withMaxRestarts(3)
            .withResetBackoffAfter(10.seconds)
        ).receiveSignal {
          case (state, RecoveryCompleted) =>
            ctx.log.info("**************Recovery Completed with state: {}***************",state)
          case (state, SnapshotCompleted(meta))  =>
            ctx.log.info("**************Snapshot Completed with state: {},id({},{})***************",state,meta.persistenceId, meta.sequenceNr)
          case (state,RecoveryFailed(err)) =>
            ctx.log.error("recovery failed with: {}",err.getMessage)
          case (state,SnapshotFailed(meta,err)) =>
            ctx.log.error("snapshoting failed with: {}",err.getMessage)
        }.snapshotWhen {
          case (state,CartPaid,seqnum) =>
            ctx.log.info("*****************snapshot taken at: {} with state: {}",seqnum,state)
            true
          case (state,event,seqnum) => false
        }.withRetention(RetentionCriteria.snapshotEvery(numberOfEvents = 100, keepNSnapshots = 2))
      }
    ).onFailure(
      SupervisorStrategy
        .restartWithBackoff(minBackoff = 10.seconds, maxBackoff = 60.seconds, randomFactor = 0.1)
        .withMaxRestarts(3)
        .withResetBackoffAfter(10.seconds)
    )
}

object Shopper {

  import ItemInfo._

  sealed trait Command extends CborSerializable

  case class GetItem(item: Item) extends Command
  case object Settle extends Command
  case object GetCount extends Command

  case class WrappedResponse(res: MyCart.Response) extends Command

  def apply(): Behavior[Command] = Behaviors.setup[Command] { ctx =>
    val shoppingCart = ctx.spawn(MyCart(), "shopping-cart")
    val cartRef: ActorRef[MyCart.Response] = ctx.messageAdapter(WrappedResponse)
    Behaviors.receiveMessage { msg =>
      msg match {
        case GetItem(item) =>
          shoppingCart ! MyCart.AddItem(item)
        case Settle =>
          shoppingCart ! MyCart.PayCart
        case GetCount =>
          shoppingCart ! MyCart.CountItems(cartRef)
        case WrappedResponse(res) => res match {
          case MyCart.PickedItems(items) =>
            ctx.log.info("**************Current Items in Cart: {}*************", items)
          case MyCart.CartEmpty =>
            ctx.log.info("**************shopping cart is empty!***************")
        }
      }
      Behaviors.same
    }
  }

}


object ShoppingCart extends App {
  import ItemInfo._
  val shopper = ActorSystem(Shopper(),"shopper")
  shopper ! Shopper.GetItem(Item("banana",11.20))
  shopper ! Shopper.GetItem(Item("watermelon",4.70))
  shopper ! Shopper.GetCount
  shopper ! Shopper.Settle
  shopper ! Shopper.GetCount
  scala.io.StdIn.readLine()

  shopper.terminate()

}

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※回頭車貨運收費標準

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※推薦評價好的iphone維修中心

※教你寫出一流的銷售文案?

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

殼牌石油拚減碳 誓言最遲2050年達零排放

摘錄自2020年4月16日聯合報報導

國際石油天然氣巨擘荷蘭皇家殼牌集團(Royal Dutch Shell)今(16日)誓言,在2050年前要達成「碳中和」(Carbon Neutral)目標,和競爭對手英國石油公司(BP)的承諾一樣。

法新社報導,殼牌執行長范柏登(Ben vanBeurden)在聲明中表示,社會對於氣候變遷的期許瞬息萬變,殼牌需要再進一步自我要求,計畫最晚在2050年成為零排放的能源企業。殼牌將在2050年前把自家能源產品的「淨碳足跡」減少約65%;在2030年減少30%。

溫室氣體
能源議題
全球變遷
氣候變遷
能源轉型
國際新聞
殼牌
減碳宣言
零排放
石油

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

※推薦評價好的iphone維修中心

網頁設計最專業,超強功能平台可客製化

※別再煩惱如何寫文案,掌握八大原則!

日本大鯢數量急減 雜交問題嚴重 水族館推大鯢布偶提升國民關注

文:宋瑞文

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

※回頭車貨運收費標準

太空可見!威尼斯疫情前後巨大變化 衛星對比照曝光

摘錄自2020年4月17日自由時報報導

武漢肺炎疫情持續蔓延,各國經濟無不受到影響,但人類社會活動暫停對地球的正面影響也已明顯出現,歐洲太空總署近日公布義大利水都威尼斯的衛星空拍對比照,只見去年同期與今年同期相比,當地水質狀況截然不同。

根據《CNN》報導,歐洲太空總署(European Space Agency)近日公布由Sentinel-2衛星拍攝的威尼斯空拍照,只見當地去年4月19日時水質十分混濁,鄰近水域幾乎都是黃澄澄一片泥沙,並有大量船隻在運行;作為對比,今年4月13日該水域已清澈見底。

威尼斯衛星空拍對比照。照片來源:
歐洲太空總署( CC BY-SA 3.0 IGO)


土地水文
土地利用
生物多樣性
國際新聞
義大利
威尼斯
水質
動物與大環境變遷
武漢肺炎
水文

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

網頁設計最專業,超強功能平台可客製化

※回頭車貨運收費標準

加州紅潮「藍影浮動」 夜光藻美麗的背後隱藏殺機

摘錄自2020年04月19日ETtoday新聞雲美國報導

佛羅里達西南部海域自2017年10月開始出現紅潮,微小藻類引起這種有害的藻華現象(algal booms),不同的藻類造成不同顏色。根據《CNN》報導,這位遊客在加州的紐波特海灘(Newport Beach)目睹了紅潮,海水中的「夜光藻」讓海水呈現「藍色螢光」。如果海岸一片平靜倒看不出異常,但隨著潮水拍岸,海中的藻華也隨之翻湧,浮現魅惑的亮藍色,在黑夜中宛如海中銀河。

紅潮讓許多遊客趨之若鶩,離島馬祖也有如此美景可欣賞。然而美麗的背後卻伴隨著死亡,根據《國家地理頻道》報導,佛羅里達南部的海灘被成千海洋生物的屍體覆蓋,各種魚類、螃蟹、海牛、海豚、海龜等生物,因為吃下大量海藻而中毒。

而研究指出,人為排放的養分是造成藻華數目急遽上升的原因,而大型風暴通過的地區也可能出現紅潮異常的情況。然而目前對於紅潮是「人為」還是「天然」造成仍存在爭議。

全球變遷
氣候變遷
國際新聞
加州
紅潮
藻華現象

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

※教你寫出一流的銷售文案?

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

鋁複合材料能存氣體 氫氣車電池新招

摘錄自2020年4月19日聯合新聞網報導

美國西北大學法爾哈教授所帶領的團隊,最近成功研發一種以鋁做為主元素的複合材料:NU-1501,有望成為氫氣車大量儲存氣體的新選項。該材料在結構上與海綿相似,擁有許多小孔可儲存氣體,NU-1501可透過壓力的不同來儲存或釋放氣體。比起現有的氣體儲存槽更加安全、輕量與便宜,若加以應用,有極大潛力。

為了降低交通運輸業所造成的碳排放,可達到零碳排的氫氣車近年來成為重要的研究主軸。氫氣車主要透過氫和氧的化學反應來產生電,過程並不會產生任何二氧化碳。但因為氫氣十分易燃且體積龐大,在未加壓的狀況下,跑100公里需要11,000公升的氫氣。

目前業界主要採取將氫氣加壓到700巴,比汽車胎壓還要高出300倍,除了有安全疑慮外,特別設計過的氣體儲存槽也相當笨重與昂貴。而NU-1051除了能攜帶更多的氫氣外,也只需要比較低的壓力,因此十分具有發展潛力。

能源議題
環境經濟
能源轉型
循環經濟
國際新聞
氫氣車
海綿

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※想知道最厲害的網頁設計公司"嚨底家"!

※別再煩惱如何寫文案,掌握八大原則!

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※回頭車貨運收費標準

台中搬家公司費用怎麼算?

十來萬買車要能上班還能自駕游,你的城市適合哪輛車?

廣汽菲克-Jeep自由本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※別再煩惱如何寫文案,掌握八大原則!

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

※教你寫出一流的銷售文案?

網頁設計最專業,超強功能平台可客製化

※產品缺大量曝光嗎?你需要的是一流包裝設計!

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

聽說這是20萬內老婆最滿意的車

它還是會打一個激靈再往前沖。不過調到運動模式之後,你才會更深的感受到這輛車的動力。儀錶盤隨即變為紅色主題,好像是有點鋼炮的意思。只不過它增加的是心理馬力,但是對於速度的提升還是很明顯的。V40的懸挂調校是那種偏硬的感覺,對於車身的支撐是相當足,但是舒適性和1系來比會打點折扣。

自從吉利把沃爾沃收了之後,大家對這個北歐品牌的熟悉程度就更高了。只不過知名度歸知名度,要真是讓你買車選一輛沃爾沃,可能大家都未必願意。所以今天就給大家試一下它的入門級車型-V40。

就在試駕車剛剛到公司,小喬就撲了上去,並引發了以下對話…

既然是北歐的廠商,那必然要帶有北歐特色才行。沒錯,“雷神之錘”大家都聽說過了吧,說的就是沃爾沃的頭燈,V40打開日間行車燈后,視覺效果確實很不錯,但這種畫風更適合年輕買家。相對於能見度較高的A3、1系或A級來說,V40就是小眾但不失氣質的存在。

V40提供了T3/T4/T5三個代號的車型選擇,分別搭載1.5T/2.0T四缸發動機。畢竟是輛買菜車,V40的動力輸出都是相當穩當般的存在。正常模式下想急加速?它還是會打一個激靈再往前沖。

不過調到運動模式之後,你才會更深的感受到這輛車的動力。儀錶盤隨即變為紅色主題,好像是有點鋼炮的意思。只不過它增加的是心理馬力,但是對於速度的提升還是很明顯的。

V40的懸挂調校是那種偏硬的感覺,對於車身的支撐是相當足,但是舒適性和1系來比會打點折扣。值得一提的是V40的隔音相當不錯,但問題是這個不錯只局限於風噪。正常行駛下路面的噪聲就直接從車底傳進來,這很尷尬,但也沒辦法。

V40的內飾還是保持了一貫的沃爾沃風格。最明顯的就是中控台萬年不變的数字鍵。而它又長又大的手剎把就成為了中控台的一道風景。

反觀V40的空間表現,還是能讓家人滿意的。最起碼它的後排空間沒有給人覺得任何憋屈的感覺。

V40作為一輛入門級車型,官方售價終端為18.89-30.99萬元,對於那些追求獨特的年輕車主來說,這是一個不小的誘惑。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

※別再煩惱如何寫文案,掌握八大原則!

※回頭車貨運收費標準

※教你寫出一流的銷售文案?

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

網頁設計最專業,超強功能平台可客製化

城市SUV就不能越野?進山後這台車性格大變

無疆界。盡馳騁本次試駕的地點選址是廣州王子山,奧迪本次並沒有專門為試駕活動搭建一塊可以輕鬆通過考驗的場地,而是以不常規的純天然全路況山林作為試驗全新奧迪Q7的考試場。在鋪裝路面上,將空氣懸架調節至最低狀態,此時離地間隙為186mm,車身姿態更低,高速狀態下行駛異常穩定,2。

廣州王子山奧迪Q7任性越野

奧迪Q7的形象一直以來都給人一種優雅穩重,大氣成熟的印象。似乎它的車主就應該西裝革履一本正經的出入五星級酒店和出席高檔商務場所。然而很多人並不知道,當它脫離了城市的喧囂和繁華之後,在崎嶇難行的非鋪裝路面,奧迪Q7也會露出崢嶸任性的一面。

初現肌肉——粗獷中的精緻

全新奧迪Q7上市已經有一段時間,相比較上一代奧迪Q7,全新的奧迪Q7在外觀層面進行了“全面瘦身”,車身尺寸有所減小,儘管在很多人眼裡似乎全新的奧迪Q7少了一分霸氣,但是個人認為,更加平直簡練的線條,使得全新奧迪Q7更多了一分精緻感。如果說老款的奧迪Q7更像一個兇猛的肌肉男,那麼全新奧迪Q7則更像一個肌肉線條更加勻稱卻不失力量感的健美先生。

凸顯內涵——小尺寸中的大肚量

全新奧迪Q7的內飾採用了全新的設計語言,整體造型設計更加簡潔明快,作為一款奧迪品牌旗艦型的SUV,細節之處的精細程度毋庸置疑。

鋁製和木紋飾板的搭配,彰顯了豪華感的同時更體現出現代工藝氣息,搭配上第二代的MMI智能信息系統以及點火之後緩緩升起的8.3英寸液晶显示屏,檔次感豪華感與科技感並存的車廂確實讓人覺得相當高級。

不少人會覺得,全新Q7的車身尺寸全面縮水,會不會提升Q7車內的乘坐壓抑感,帶着疑問小編也是坐進全新Q7的後排體驗一番,實際感受過後覺得並不會,全新Q7的長寬高數據為:5069*1968*1716mm,軸距長為3001mm,空間足夠寬敞,加之座椅十分舒適度也算不錯,駕駛位坐姿設計合理,視野盲區不大,全新Q7的駕乘感受在同級當中可謂是處於領先水準。

裝備齊全才能游刃有餘

全新奧迪Q7的標準離地間隙達到201mm,在一般的道路通過性上已經有着非常紮實的基礎,並且搭載quattro全時四驅系統以及可變式空氣懸挂(非頂配車型需選裝),在不同的道路模式下有着不同的行駛姿態,以及百公里加速僅需7.22秒的2.0T渦輪增壓發動機,強悍的性能和齊全的裝備,讓奧迪Q7在面對極限路況時才有着充足的信心和底氣。

無疆界;盡馳騁

本次試駕的地點選址是廣州王子山,奧迪本次並沒有專門為試駕活動搭建一塊可以輕鬆通過考驗的場地,而是以不常規的純天然全路況山林作為試驗全新奧迪Q7的考試場。

在鋪裝路面上,將空氣懸架調節至最低狀態,此時離地間隙為186mm,車身姿態更低,高速狀態下行駛異常穩定,2.0T的發動機並不會顯得拖沓無力,隨着油門的深入,到達1600轉之時所迸發出的370牛米的峰值扭矩,儘管它是一台大尺寸豪華SUV,但也能讓駕駛者充分體驗一把速度與激情。

既然作為一款搭載着全時四驅和可變式空氣懸架的大尺寸SUV,在王子山這種純天然的山路中行進既是作為一個較為嚴酷的考驗項目,也是全新Q7展現強悍性能的用武之地。面對着崎嶇的道路環境,將氣動懸架的高度提升至最高狀態(261mm),高聳的底盤極大降低了因為道路石塊而托底的風險。

得益於調校得恰到好處的轉向手感,在速度不快的越野道路上,大尺寸的Q7顯得異常靈活,穩定的車身姿態使得在路況較為驚險的非鋪裝路面上有着十足的信心支撐。

王子山上的路況十分複雜,由於嶺南一帶潮濕的氣候,儘管白天陽光十分燦爛,但是路面依然有不少地方非常濕滑,甚至還有一些淌流着的小溪和溝壑需要跨越,但是全新奧迪Q7面對這些對於普通城市SUV來說已經算是極限的道路,依然顯得游刃有餘,勝似閑庭信步。

這是由於奧迪Q7的前後橋都配備了了開放式差速器和輪間限滑系統,並且quattro四驅在動力配比上日常是前後橋40:60的比例分攤動力輸出,而在極限狀況下可以使得前橋最大動力供給達到70%,而後橋可支配動力最高可以達到85%,極大保證了全新Q7面對惡劣路況的適應性。

檔次不減;盛名不負

以往對於奧迪Q7的形象一直是一台大品牌、高品質、高檔次的豪華型城市SUV,似乎它的一切就應該那麼有條不紊,似乎它就一定是一款在公路上馳騁的大塊頭豪華車。

然而畢竟作為一款旗艦型全時四驅SUV,奧迪Q7的先天基因依然流淌着一款SUV該有的性格與野性,不受“城市SUV”標準的束縛、不受“全民SUV”時代的限制,憑藉著強悍的性能表現,奧迪Q7,一款豪華品牌旗艦SUV,除了可以帶來頂級奢華的高檔感以外,照樣可以讓車主縱情翻山越嶺感受喧囂以外的非凡。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計最專業,超強功能平台可客製化

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※回頭車貨運收費標準

※推薦評價好的iphone維修中心

※教你寫出一流的銷售文案?

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

台中搬家公司費用怎麼算?

動力橫評|跑進10秒 海馬S5領先不止一點

在0-100公里加速上,海馬S5-9。81秒、長安CS35-11。02秒、寶駿560-12。01秒。從加速上來看,在8萬級別中,能進入10秒的真是屈指可數。相比長安CS35和寶駿560來說,在整個加速過程中,海馬S5在起步的瞬間動力來得更加直接,而且在中後段也不會出現乏力情況。

在市場表現上,售價8萬至10萬的8萬級SUV近兩年相當火爆。針對上述情況,找了能見度比較高的三款八萬級熱銷車型,在動力方面做個實測對比,(為什麼對比動力?因為注重內涵,外觀下次測)這次對比的三款車型分別是:長安CS35、海馬S5、寶駿560、均是1.5T動力總成

從上表的綜合數據來看,三者的動力系統都算主流,不謙虛地說也算是這個級別里相對靠前的,不過海馬S5的參數表現都要略優於其它兩款車型。三款對比車型均採用1.5T渦輪增壓發動機,海馬S5的最大馬力為163ps、最大扭矩223N·m;寶駿560的最大馬力150ps,最大扭矩230N·m;長安CS35的最大馬力156ps,最大扭矩215N·m。從賬面數據來看,海馬S5的優勢非常明顯。

此外,海馬S5的1.5T渦輪增壓發動機採用了新一代小慣量渦輪,恭弘=叶 恭弘輪採用最新一代合金材料,重量更輕,慣量降低35%,有效減小渦輪遲滯,提高系統響應性,滿足低轉速大扭矩驅動,發動機1600轉時就能輸出90%最大扭矩。

從發動機的動力表現,三者在實際駕駛中,均有着不錯的親和力,起步反饋輕快,低速行駛時動力與變速箱的配合也相當不錯。但海馬S5在動力上的體驗會來得更加地直接,當發動機轉速僅為1000rpm時,就能體驗到渦輪介入,在1800rpm狀態下,即能體驗到223N·m的最大扭矩輸出狀態,且能一直保持到4000rpm,動力輸出水平在同級車型中相當出色,且最大扭矩轉速區間很寬。相比之下,寶駿560的1.5T發動機,在2000rpm時才能達到最大扭矩輸出,最大扭矩轉速區間為2000-3800rpm,明顯劣於海馬S5。

在0-100公里加速上,海馬S5-9.81秒、長安CS35-11.02秒、寶駿560-12.01秒。從加速上來看,在8萬級別中,能進入10秒的真是屈指可數。相比長安CS35和寶駿560來說,在整個加速過程中,海馬S5在起步的瞬間動力來得更加直接,而且在中後段也不會出現乏力情況。

在剎車對比測試中,海馬S5進入了40米內,如此優秀的剎車成績,可以讓它在大部分同級別、同價位的競爭對手面前炫耀一番。須知道這個級別大部分車型的100-0km/h剎車距離都是40米開外,有的甚至是43米之多。除了剎車成績優秀以外,其剎車踏板反饋回來的腳感也很棒,不僅虛位小,而且回饋力度比較均勻線性。

從動力輸出到制動力的表現,可以看出在三款車型當中,海馬S5數據和實力明顯優於寶駿560和長安CS35。

寶駿560-低速扭矩力量足夠,1擋到4擋的提速感覺明顯有力,但是進入5擋,時速超過100公里/小時的時候,提速就會顯得很乏力。

長安CS35-發機機是三菱的4G15T改造而來,加速只能說夠用,不能深踩,高轉速時發動機的聲音讓人奔潰。

海馬S5-上車地板油S檔,兩千轉加速明顯,還有推背感,雖然不算強,但能有這個的體驗反饋給你,後段的力量儲備也是相對充足,在賽道直路最快跑165時速,底盤不會給你坐船的感覺,懸挂也是支撐到位,在高轉速時,發動機也是會有聲音但是,在一個可接受範圍。

回看市場,如今小型SUV由不入流,到成為主流,最多也不過5年,但是市場卻發生了翻天覆地的變化的變化,如今的小型SUV市場競爭已然也到了白熱化的地步。縱觀國內小型SUV細分市場,本田、現代/起亞、別克、鈴木、福特、雪佛蘭、Jeep等國際大廠都推出了合資小型SUV,在品質上力壓自主車型,同時價錢也在不斷下探,新老車型的格局正在逐漸形成,而這種格局勢必會引起小型SUV市場的一些變革,花無百日紅,新生代小型SUV正在陸續到來,如果產品力可以經得起考驗的情況下,小型SUV市場或將很快迎來一次大換血,讓我們拭目以待吧。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※回頭車貨運收費標準

※推薦評價好的iphone維修中心

※超省錢租車方案

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

※推薦台中搬家公司優質服務,可到府估價