不畏離岸風電陰霾 4公股海運結盟投入海事工程

摘錄自2019年1月10日中央廣播電台報導

由於今年離岸風電躉購費率可能大降,已有2家外資開發商暫停現有規畫,重新評估,為台灣離岸風電發展增添不確定性。不過,台船、陽明海運、台灣港務港勤、台灣航業等4家本土公股海運10日簽署四方合作意向書,宣布以公股企業聯盟之姿,透過離岸風電海事工程運輸服務來切入風場開發案,不但將海事工程本土團隊推上檯面,同時,也宣告台灣工程界正式邁入海事工程的新紀元。

聯盟集結各家所長,像是台灣港務港勤掌握台灣所有碼頭使用分配,還擁有拖船、測量船、啟動船、修船設施等優勢,陽明海運則有堅實的船舶維運、船員管理等基礎,目前聯盟會先組成工作小組做整合,未來甚至不排除共同成立公司。

本站聲明:網站內容來源於裝修網https://e-info.org.tw/,如有侵權,請聯繫我們本站聲明:網站內容來源再生能源資訊網https://e-info.org.tw/,如有侵權請聯繫我們,我們將及時處理本站聲明:網站內容來源再生能源資訊網https://e-info.org.tw/,如有侵權請聯繫我們,我們將及時處理

 

【其他文章推薦】

示波器探測執行效能最佳化的8大秘訣

測試專家告訴你如何好好使用示波器

影響示波器測試準確度的五大因素

選擇示波器的10 項考量因素

網紅教學「吃鳳梨妹妹會變香」全網瘋傳! 女同事「實測7天後驚人結果」醫生傻眼:女生要小心

如今網際網路越來越便利,只要動動滑鼠及鍵盤,就能查詢到任何想找的資料,然而網路上卻也流傳著不實謠言,若是真的採信,可能會對你的生活造成極大的困擾。日前就有一位女網友因為網路流傳「吃鳳梨會讓私密處變香」,因此她便與同事大量團購鳳梨,想不到連吃7天後就發生悲劇了… 

 

原PO 在臉書《爆怨公社》發文提到,近日公司LINE群組和朋友群組流傳一段影片,影片內容是某位網紅空姐教女人保養私密處的方法,這位空姐表示常吃蘑菇會讓妹妹變臭,吃鳳梨則是會讓私密處散發香味,且分泌物會有甜甜的味道。 


圖/擷取自youtube

 

雖然原PO平常保養得宜,沒有私密處散發異味的困擾,但聽到這件事卻感到十分新奇,所以便和同事購買大量鳳梨食用。 

 

萬萬沒想到,連續吃7天鳳梨後,她的私密處味道不但沒有任何變化,分泌物卻反而變多,「就是私密處流很多很多的,類似透明膠水那種感覺。」使得內褲上全是痕跡,夏天悶熱還得墊上護墊,讓她感到十分不適,再也不敢隨意相信網路謠言。 

 

貼文一出,立刻掀起熱烈討論,網友紛紛表示「不管吃什麼東西都要適量啦」、「每個人體質都不一樣啊」 、「網路謠言真的很可怕」、「吃清淡一點會更有效果」、「謝謝妳的實驗報告」、「我之前也有一陣子很愛吃鳳梨,也是出現像妳一樣的分泌物,去看中醫醫師說因為鳳梨太冷了,分泌物才會這麼多。」 

 

據《ETtoday》報導,由於鳳梨富含維生素C、且具有抗微生物的特性,再加上鳳梨為酸性,有助於讓陰道維持微酸性,不過每個女生的體質都不盡相同,食用還是適量為佳,要是出現任何異狀,更是要盡快向醫生諮詢求助唷! 

雖然大家都不希望私密處出現搔癢或散發異味的困擾,但這時依然不要隨意聽信網路偏方,要是有出現狀況還是找醫生求助,才能夠有效解決問題喔~

來源:Ettoday、EBC、Health Magazine、Live Science

本站聲明:網站內容來源http://www.look543.com,如有侵權,請聯繫我們,我們將及時處理

電子菸
電子煙
電子菸

CQRS+ES項目解析-Diary.CQRS

在《當我們在討論CQRS時,我們在討論些神馬》中,我們討論了當使用CQRS的過程中,需要關心的一些問題。其中與CQRS關聯最為緊密的模式莫過於Event Sourcing了,CQRS與ES的結合,為我們構造高性能、可擴展系統提供了基本思路。本文將介紹
Kanasz Robert在《Introduction to CQRS》中的示例項目Diary.CQRS。

獲取Diary.CQRS項目

該項目為Kanasz Robert為了介紹CQRS模式而寫的一個測試項目,原始項目可以通過訪問《Introduction to CQRS》來獲取,由於項目版本比較舊,沒有使用nuget管理程序包等,導致下載以後並不能正常運行,我下載了這個項目,升級到Visual Studio 2017,重新引用了StructMap框架(使用nuget),移除了Web層報錯的代碼,並上傳到博客園,可以從這裏下載:Diary.CQRS.rar

Diary.CQRS項目簡介

Diary.CQRS項目的場景為日記本管理,提供了新增、編輯、刪除、列表等功能,整個解決方案分為三個項目:

  • Diary.CQRS:核心項目,完成了EventBus、CommandBus、Domain、Storage等功能,也是我們分析的重點。
  • Diary.CQRS.Configuration:服務配置,通過ServiceLocator類進行依賴注入、服務查找功能。
  • Diary.CQRS.Web:用戶界面,MVC項目。

這是一個很好的入門項目,功能簡單、結構清晰,概念覆蓋全面。如果CQRS是一個城堡,那麼Diary.CQRS則是打開第一重門的鑰匙,接下來讓我們一起推開這扇門吧。

Diary.CQRS.Web

運行項目,最先看到的是一個Web頁面,如下圖:

很簡單,只有一個Add按鈕,當我們點擊以後,會進入添加的頁面:

我們填上一些內容,然後點擊Save按鈕,就會返回到列表頁,我們可以看到已添加的條目:

然後我們進行編輯操作,點擊列表中的Edit按鈕,跳轉到編輯頁面:

雖然頁面中显示的是Add,但確實是Edit頁面。我們編輯以後點擊Save按鈕,然後返回列表頁即可看到編輯后的內容。

在列表頁中,如果我們點擊Delete按鈕,則會刪除改條目。

到此為止,我們已經看到了這個項目的所有頁面,一個簡單的CURD操作。我們繼續看它的代碼(在HomeController中)。

Index:列表頁面

public ActionResult Index()
{
    ViewBag.Model = ServiceLocator.ReportDatabase.GetItems();
    return View();
}

通過ServiceLocator定位ReportDatabase,並從ReportDatabase中獲取所有條目。

Add:新增頁面

public ActionResult Add()
{
    return View();
}

[HttpPost]
public ActionResult Add(DiaryItemDto item)
{
    ServiceLocator.CommandBus.Send(new CreateItemCommand(Guid.NewGuid(), item.Title, item.Description, -1, item.From, item.To));
    return RedirectToAction("Index");
}

兩個方法:

  • Add()方法,處理Get請求,返回新增視圖;
  • Add(DiaryItemDto item)方法,接收DiaryItemDto參數,處理Post請求,創建併發送CreateItemCommand命令,然後返回到Index頁面

Edit:編輯頁面

public ActionResult Edit(Guid id)
{
    var item = ServiceLocator.ReportDatabase.GetById(id);
    var model = new DiaryItemDto()
    {
        Description = item.Description,
        From = item.From,
        Id = item.Id,
        Title = item.Title,
        To = item.To,
        Version = item.Version
    };
    return View(model);
}

[HttpPost]
public ActionResult Edit(DiaryItemDto item)
{
    ServiceLocator.CommandBus.Send(new ChangeItemCommand(item.Id, item.Title, item.Description, item.From, item.To, item.Version));
    return RedirectToAction("Index");
}

仍然是兩個方法:

  • Edit(Guid id)方法,接收Guid作為參數,並從ReportDatabase中獲取數據,構建dto對象返回給頁面
  • Edit(DiaryItemDto item)方法,接收DiaryItemDto對象,處理Post請求,接收到請求以後根據dto對象創建ChangeItemCommand命令,然後返回到Index頁面

Delete:刪除操作

public ActionResult Delete(Guid id)
{
    var item = ServiceLocator.ReportDatabase.GetById(id);
    ServiceLocator.CommandBus.Send(new DeleteItemCommand(item.Id, item.Version));
    return RedirectToAction("Index");
}

對於刪除操作來說,它沒有視圖頁面,接收到請求以後,先獲取該記錄,創建併發送DeleteImteCommand命令,然後返回到Index頁面

題外話:對於改變數據狀態的操作,使用Get請求是不可取的,可能存在安全隱患

通過上面的代碼,你會發現所有的操作都是從ServiceLocator發起的,通過它我們能夠定位到CommandBus和ReportDatabase,從而進行相應的操作,我們在接下來會介紹ServiceLocator類。

Diary.CQRS.Configuration

Diary.CQRS.Configuration 項目中定義了ServiceLocator類,這個類的作用是完成IoC容器的服務註冊、服務定位功能。例如我們可以通過ServiceLocator獲取到CommandBus實例、獲取ReportDatabase實例。

服務註冊

ServiceLocator使用StructureMap作為依賴注入框架,提供了服務註冊、服務導航的功能。ServiceLocator類通過靜態構造函數完成對服務註冊和服務實例化工作:

static ServiceLocator()
{
    if (!_isInitialized)
    {
        lock (_lockThis)
        {
            ContainerBootstrapper.BootstrapStructureMap();
            _commandBus = ObjectFactory.GetInstance<ICommandBus>();
            _reportDatabase = ObjectFactory.GetInstance<IReportDatabase>();
            _isInitialized = true;
        }
    }
}

首先調用ContainerBootstrapper.BootstrapStructureMap()方法,這個方法裡面包含了對將服務添加到容器的代碼;然後使用容器創建CommandBus和ReportDatabase的實例。

  • CommandBus:命令總線,對應Command操作,用來發送命令,程序中需要定義相應的命令處理器,從而完成具體的操作。
  • ReportDatabase:報表數據庫,對應Query操作,用來獲取數據。

ServiceLocator的重要之處在於對外暴露了兩個至關重要的實例,分別處理CQRS中的Command和Query。

為什麼沒有Event相關操作呢?到目前為止我們還沒有涉及到,因為對於UI層來說,用戶的意圖都是通過Command表示的,而數據的狀態變化才會觸發Event。

Diary.CQRS

在ServiceLocator中定義了獲取CommandBus和ReportDatabase的方法,我們順着這兩個對象繼續分析。

CommandBus

在基於消息的系統設計中,我們常會看到總線的身影,Command也是一種消息,所以使用總線是再合適不過的了。CommandBus就是我們在Diary.CQRS項目中用到的一種消息總線。

在Diary.CQRS中,它被定義在Messaging目錄,在這個目錄下面,還有與Event相關的EventBus,我們稍後再進行介紹。

CommandBus實現ICommandBus接口,ICommandBus接口的定義如下:

public interface ICommandBus
{
    void Send<T>(T command) where T : Command;
}

它只包含了Send方法,用來將命令發送到對應的處理程序。

CommandBus是ICommand的實現,具體代碼如下:

public class CommandBus:ICommandBus
{
    private readonly ICommandHandlerFactory _commandHandlerFactory;

    public CommandBus(ICommandHandlerFactory commandHandlerFactory)
    {
        _commandHandlerFactory = commandHandlerFactory;
    }

    public void Send<T>(T command) where T : Command
    {
        var handler = _commandHandlerFactory.GetHandler<T>();
        if (handler!=null)
        {
            handler.Execute(command);
        }
        else
        {
            throw new Exception();
        }
    }
}

在CommandBus中,顯式依賴ICommandHandlerFactory類,通過構造函數進行注入。那麼 _commandHandlerFactory 的作用是什麼呢?我們在Send方法中可以看到,通過 _commandHandlerFactory 可以獲取到與Command對應的CommandHandler(命令處理程序),在程序的設計上,每一個Command都會有一個對應的CommandHandler,而手工判斷類型、實例化處理程序顯然不符合使用習慣,此處採用工廠模式來獲取命令處理程序。

當獲取到與Command對應的CommandHandler后,調用handler的Execute方法,執行該命令。

截止目前為止,我們又接觸了三個概念:CommandHandlerFactory、CommandHandler、Command:

  • CommandHandlerFactory:命令處理程序工廠,通過GetHandler方法獲取到與命令對應的處理程序
  • CommandHandler:命令處理程序,用於執行對應的命令
  • Command:命令,描述用戶的意圖、並包含與意圖相關的數據

CommandHandlerFactory

使用簡單工廠模式,用來獲取與命令對應的處理程序。它的代碼在Utils文件夾中,它的作用是提供一種獲取Handler的方式,所以它只能作為工具存在。

接口定義如下:

public interface ICommandHandlerFactory
{
    ICommandHandler<T> GetHandler<T>() where T : Command;
}

只有GetHandler一個方法,它的實現是 StructureMapCommandHandlerFactory,即通過StructureMap作為依賴注入框架來實現的,代碼也比較簡單,這裏不再貼出來了。

Command和CommandHandler

命令是代表用戶的意圖、並包含與意圖相關的數據,比如用戶想要添加一條數據,這便是一個意圖,於是就有了CreateItemCommand,用戶要在界面上填寫添加操作必須的數據,於是就有了命令的屬性。

關於命令的定義如下:

public interface ICommand
{
    Guid Id { get; }
}

public class Command : ICommand
{
    public Guid Id { get; private set; }
    public int Version { get; set; }

    public Command(Guid id, int version)
    {
        Id = id;
        Version = version;
    }
}
  • ICommand接口:包含Id屬性,這個Id表示Command對應聚合的Id。聚合是領域驅動開發(DDD)的概念,表示一組強關聯的領域對象,而對聚合中狀態的變更,只能通過聚合根(AggregateRoot)來完成。
  • Command類:實現了ICommand接口,並增加了Version屬性,用來標記當前操作對應的聚合跟的版本。

    為什麼要有版本的概念的?因為當使用ES模式的時候,數據庫中的數據都是事件產生的數據鏡像,保存了某個時間點的數據快照,如果要獲取到最新的數據,則需要通過加載該聚合根對應的所有Event來回放到最新狀態。如果引入版本的概念,每一個Event對應一個版本,而景象中的數據也有一個版本,在進行回放的時候,可以僅加載高版本的Event進行回放,節省了系統資源,並提高了運行效率。

命令處理程序,它的作用是處理與它相對應的命令,處理CQRS的核心,接口定義如下:

public interface ICommandHandler<TCommand> where TCommand : Command
{
    void Execute(TCommand command);
}

它接收command作為參數,執行該命令的處理邏輯。每一個命令都有一個與之對應的處理程序。

我們再重新梳理一下流程,首先用戶要新增一個數據,點擊保存按鈕后,生成CreateItemCommand命令,隨後這個命令被發送到CommandBus中,CommandBus通過CommandHandlerFactory找到該Command的處理程序,此時在CommandBus的Send方法中,我們有一個Command和CommandHandler,然後調用CommandHandler的Execute方法,即完成了該方法的處理。至此,Command的處理流程完結。

CreateItemCommand和CreateItemCommandHandler

我們來看一下CreateItemCommand的代碼:

public class CreateItemCommand : Command
{
    public string Title { get; internal set; }
    public string Description { get; internal set; }
    public DateTime From { get; internal set; }
    public DateTime To { get; internal set; }

    public CreateItemCommand(Guid aggregateId, string title,
        string description, int version, DateTime from, DateTime to)
        : base(aggregateId, version)
    {
        Title = title;
        Description = description;
        From = from;
        To = to;
    }
}

它繼承自Command基類,繼承后即擁有了Id和Version屬性,然後又定義了幾個其它的屬性。它只包含數據,與該命令對應的處理程序叫做CreateItemCommandHandler,代碼如下:

public class CreateItemCommandHandler : ICommandHandler<CreateItemCommand>
{
    private IRepository<DiaryItem> _repository;

    public CreateItemCommandHandler(IRepository<DiaryItem> repository)
    {
        _repository = repository;
    }

    public void Execute(CreateItemCommand command)
    {
        if (command == null)
        {
            throw new Exception();
        }
        if (_repository == null)
        {
            throw new Exception();
        }
        var aggregate = new DiaryItem(command.Id, command.Title, command.Description, command.From, command.To);
        aggregate.Version = -1;
        _repository.Save(aggregate, aggregate.Version);
    }
}

這才是我們要分析的核心,在Handler中,我們看到了Repository,看到了DiaryItem聚合:

  • IRepository :倉儲類,代表數據的儲存方式,通過倉儲能夠進行數據操作
  • DiaryItem:領域對象,聚合根,所有數據狀態的變更只能通過聚合根來修改

在上面的代碼中,由於是新增,所以聚合的版本為-1,然後調用倉儲的Save方法進行保存。我們繼續往下扒,看看倉儲和聚合的實現。

Repository

對於Repository的定義,仍然先看一下接口中的定義,代碼如下:

public interface IRepository<T> where T : AggregateRoot, new()
{
    void Save(AggregateRoot aggregate, int expectedVersion);
    T GetById(Guid id);
}

在倉儲中只有兩個方法:

  • Save(AggregateRoot aggregate, int expectedVersion):保存期望版本的聚合根
  • GetById(Guid id):根據聚合根Id獲取聚合根

關於IRepository的實現,代碼在Repository.cs中,我們拆開來進行介紹:

private readonly IEventStorage _eventStorage;
private static object _lock = new object();

public Repository(IEventStorage eventStorage)
{
    _eventStorage = eventStorage;
}

首先是它的構造函數,強依賴IEventStorage,通過構造函數注入。EventStorage是事件的儲存倉庫,有個更為熟知的名字EventStore,我們稍後進行介紹。

public T GetById(Guid id)
{
    IEnumerable<Event> events;
    var memento = _eventStorage.GetMemento<BaseMemento>(id);
    if (memento != null)
    {
        events = _eventStorage.GetEvents(id).Where(e => e.Version >= memento.Version);
    }
    else
    {
        events = _eventStorage.GetEvents(id);
    }
    var obj = new T();
    if (memento != null)
    {
        ((IOriginator)obj).SetMemento(memento);
    }
    obj.LoadsFromHistory(events);
    return obj;
}

GetById(Guid id)方法通過Id獲取一個聚合對象,獲取一個聚合對象有以下幾個步驟:

  • 首先會從EventStorage中獲取到該聚合的快照(memento的翻譯為記憶碎片、紀念品、備忘錄,用來聚合對象的快照)。
  • 加載Event列表,加載到的事件列表將用來做事件回放。

    如果獲取到快照的話,則加載版本高於該快照版本的事件列表,如果沒有獲取到快照,則加載全部事件列表。此處在上面已經介紹過,通過快照的方式保存聚合對象,在獲取數據時可以減少重放事件的數量,起到提高加載速度的作用。

  • 實例化聚合根,對應代碼中的var obj = new T();
  • 從快照中設置聚合根的狀態。在獲取到快照以後,如果快照不為空,則調用聚合根的SetMemento方法設置為快照中的狀態,SetMemento方法定義在IOriginator接口中,聚合根需要實現該接口。
  • 加載歷史事件,完成重放。完成這個步驟以後,聚合根將更新到最新狀態。

通過這幾個步驟以後,我們得到了一個最新狀態的聚合根對象。

public void Save(AggregateRoot aggregate, int expectedVersion)
{
    if (aggregate.GetUncommittedChanges().Any())
    {
        lock (_lock)
        {
            var item = new T();
            if (expectedVersion != -1)
            {
                item = GetById(aggregate.Id);
                if (item.Version != expectedVersion)
                {
                    throw new Exception();
                }
            }
            _eventStorage.Save(aggregate);
        }
    }
}

Save方法,用來保存一個聚合根對象。在這個方法中,參數expectedVersion表示期望的版本,這裏約定-1為新增的聚合根,當聚合根為新增的時候,會直接調用EventStorage中的Save方法。

關於expectedVersion參數,我們可以理解為對併發的控制,只有當expectedVersion與GetById獲取到的聚合根對象的版本相同時才能進行保存操作。

在介紹Repository類的時候,我們接觸了兩個新的概念:EventStorage和AggregateRoot,接下來我們分別進行介紹。

AggregateRoot

AggregateRoot是聚合根,他表示一組強關聯的領域對象,所有對象的狀態變更只能通過聚合根來完成,這樣可以保證數據的一致性,以及減少併發衝突。應用到EventSourcing模式中,聚合根的好處也是很明顯的,我們所有對數據狀態的變更都通過聚合根完成,而每次變更,聚合根都會生成相應的事件,在進行事件回放的時候,又通過聚合根來完成歷史事件的加載。由此我們可以看到,聚合根對象應該具備生成事件、重放事件的能力。

我們來看看聚合根基類的定義,在Domain文件夾中:

public abstract class AggregateRoot : IEventProvider{
    // ......
}

首先這是一個抽象類,實現了IEventProvider接口,該接口的定義如下:

public interface IEventProvider
{
    void LoadsFromHistory(IEnumerable<Event> history);
    IEnumerable<Event> GetUncommittedChanges();
}

它定義了兩個方法,我們分別進行說明:

  • LoadsFromHistory()方法:加載歷史事件,還原聚合根的最新狀態,我們在Repository中已經用過這個方法。
  • GetUncommittedChanges()方法:獲取未提交的事件。一個命令可能造成聚合根發生多次更改,每次更改都會產生一個事件,這些事件被暫時的保存在聚合根對象中,通過該方法可以獲取到未提交的事件列表。

為了實現這個接口,聚合根中定義了 List<Event> _changes對象,用來臨時存儲所有未提交的事件,該對象在構造函數中進行初始化。

AggregateRoot中對於該事件的實現如下:

public void LoadsFromHistory(IEnumerable<Event> history)
{
    foreach (var e in history)
    {
        ApplyChange(e, false);
    }
    Version = history.Last().Version;
    EventVersion = Version;
}

public IEnumerable<Event> GetUncommittedChanges()
{
    return _changes;
}

LoadsFromHistory方法遍歷歷史事件,並調用ApplyChange方法更新聚合根的狀態,在完成更新後設置版本號為最後一個事件的版本。GetUncommittedChanges方法比較簡單,返回對象的_changes事件列表。

接下來我們看看ApplyChange方法,該方法有兩個實現,代碼如下:

protected void ApplyChange(Event @event)
{
    ApplyChange(@event, true);
}

protected void ApplyChange(Event @event, bool isNew)
{
    dynamic d = this;
    d.Handle(Converter.ChangeTo(@event, @event.GetType()));
    if (isNew)
    {
        _changes.Add(@event);
    }
}

這兩個方法定義為protected,只能被子類訪問。我們可以理解為,ApplyChange(Event @event)方法為簡化操作,對第二個參數進行了默認為true的操作,然後調用ApplyChange(Event @event, bool isNew)方法。

在ApplyChange(Event @event, bool isNew)方法中,調用了聚合根的Handle方法,用來處理事件。如果isNew參數為true,則將事件添加到change列表中,如果為false,則認為是在進行事件回放,所以不進行事件的添加。

需要注意的是,聚合根的Handle方法,與EventHandler不同,當Event產生以後,首先由它對應的聚合根進行處理,因此聚合根要具備處理該事件的能力,如何具備呢?聚合根要實現IHandle接口,該接口的定義如下:

public interface IHandle<TEvent> where TEvent:Event
{
    void Handle(TEvent e);
}

這裏可以看出,IHandle接口是泛型的,它只對一個具體的Event類型生效,在代碼上的體現如下:

public class DiaryItem : AggregateRoot,
    IHandle<ItemCreatedEvent>,
    IHandle<ItemRenamedEvent>,
    IHandle<ItemFromChangedEvent>,
    IHandle<ItemToChangedEvent>,
    IHandle<ItemDescriptionChangedEvent>,
    IOriginator
{
    //......
}

最後,聚合根還定義了清除所有事件的方法,代碼如下:

public void MarkChangesAsCommitted()
{
    _changes.Clear();
}

MarkChangesAsCommitted()方法用來清空事件列表。

Event

終於到我們今天的另外一個核心內容了,Event是ES中的一等公民,所有的狀態變更最終都以Event的形式進行存儲,當我們要查看聚合根最新狀態的時候,可以通過事件回放來獲取。我們來看看Event的定義:

public interface IEvent
{
    Guid Id { get; }
}

IEvent接口定義了一個事件必須擁有唯一的Id進行標識。然後Event實現了IEvent接口:

public class Event:IEvent
{
    public int Version;
    public Guid AggregateId { get; set; }
    public Guid Id { get; private set; }
}

可以看到,除了Id屬性外,還添加了兩個字段Version和AggregateId。AggregateId表示該事件關聯的聚合根Id,通過該Id可以獲取到唯一的聚合根對象;Version表示事件發生時該事件的版本,每次產生新的事件,Version都會進行累加。

從而可以知道,在EventStorage中,聚合根Id對應的所有Event中的Version是順序累加的,按照Version進行排序可以得到事件發生的先後順序。

EventStorage

顧名思義,EventStorage是用來存儲Event的地方。在Diary.CQRS中,EventStorage的定義如下:

public interface IEventStorage
{
    IEnumerable<Event> GetEvents(Guid aggregateId);
    void Save(AggregateRoot aggregate);
    T GetMemento<T>(Guid aggregateId) where T : BaseMemento;
    void SaveMemento(BaseMemento memento);
}
  • GetEvents(Guid aggregateId):根據聚合根Id獲取該聚合根的所有事件
  • Save(AggregateRoot aggregate):保存方法,入參為聚合根對象,在實現上則是獲取聚合根中所有未提交的事件,隨後對這些事件進行處理
  • GetMemento():獲取快照
  • SaveMemento():存儲快照

Diary.CQRS中使用InMemory的方式實現了EventStorage,屬性和構造函數如下:

private List<Event> _events;
private List<BaseMemento> _mementoes;
private readonly IEventBus _eventBus;

public InMemoryEventStorage(IEventBus eventBus)
{
    _events = new List<Event>();
    _mementoes = new List<BaseMemento>();
    _eventBus = eventBus;
}
  • _events:事件列表,內存中存儲事件的位置,所有事件最終都會存儲在該列表中
  • _mementoes:快照列表,用於存儲聚合根的某個事件版本的狀態
  • _eventBus:事件總線,用於發布任務

當Event生成后,它並沒有馬上存入EventStorage,而是在Repository显示調用Save方法時,倉儲將存儲權交給了EventStorage,EventStorage是事件倉庫,事件倉儲在存儲時進行了如下操作:

  • 獲取聚合根中所有未提交的Event,同時獲取到聚合根當前的版本號
  • 遍歷未提交Event列表,根據聚合根版本號自動為Event生成版本號,保持自增長的特性;
  • 生成聚合根快照。示例中每3個版本生成一次,並保持到事件倉儲中。
  • 將任務添加到事件倉庫中。
  • 再次遍歷未提交Event列表,此時將進行任務發布,調用事件總線的Publish方法進行發布。

Save方法的代碼如下:

public void Save(AggregateRoot aggregate)
{
    var uncommittedChanges = aggregate.GetUncommittedChanges();
    var version = aggregate.Version;

    foreach (var @event in uncommittedChanges)
    {
        version++;
        if (version > 2)
        {
            if (version % 3 == 0)
            {
                var originator = (IOriginator)aggregate;
                var memento = originator.GetMemento();
                memento.Version = version;
                SaveMemento(memento);
            }
        }
        @event.Version = version;
        _events.Add(@event);
    }
    foreach (var @event in uncommittedChanges)
    {
        var desEvent = Converter.ChangeTo(@event, @event.GetType());
        _eventBus.Publish(desEvent);
    }
}

至此Event的處理流程就算完結了。此時所有的操作都是在主庫完成的,當事件被發布以後,訂閱了該事件的所有Handler都將會被觸發。

在Diary.CQRS項目中,EventHandler都被用來處理ReportDatabase了。

ReportDatabase

當你使用ES模式時,都存在一個嚴重問題,那就是數據查詢的問題。當用戶進行數據檢索是,必然會使用各種查詢條件,然而無論那種事件倉庫都很難滿足複雜查詢。為了解決此問題,ReportDatabase就顯得格外重要。

ReportDatabase的作用被定義為獲取數據、應對數據查詢、生成報表等,它的結構與主庫不同,可以根據不同的業務場景進行定義。

ReportDatabase的數據不是通過業務邏輯進行更新的,它通過訂閱Event進行更新。在本示例中ReportDatabase實現的很簡單,接口定義如下:

public interface IReportDatabase
{
    DiaryItemDto GetById(Guid id);
    void Add(DiaryItemDto item);
    void Delete(Guid id);
    List<DiaryItemDto> GetItems();
}

實現上,通過內存中維護一個列表,每次接收到事件以後,都對相應數據進行更新,此處不在貼出。

EventHandler、EventHandlerFactory和EventBus

在上文中已經介紹過Event,而針對Event的處理,實現邏輯上與Command非常相似,唯一的區別是,命令只可以有一個對應的處理程序,而事件則可以有多個處理程序。所以在EventHandlerFactory中獲取處理程序的方法返回了EventHandler列表,代碼如下:

public IEnumerable<IEventHandler<T>> GetHandlers<T>() where T : Event
{
    var handlers = GetHandlerType<T>();

    var lstHandlers = handlers.Select(handler => (IEventHandler<T>)ObjectFactory.GetInstance(handler)).ToList();
    return lstHandlers;
}

在EventBus中,如果一個事件沒有處理程序也不會引發錯誤,如果有一個或多個處理程序,則會以此調用他們的Handle方法,代碼如下:

public void Publish<T>(T @event) where T : Event
{
    var handlers = _eventHandlerFactory.GetHandlers<T>();
    foreach (var eventHandler in handlers)
    {
        eventHandler.Handle(@event);
    }
}

總結

Diary.CQRS是一個典型的CQRS+ES演示項目,通過對該項目的分析,我們能了解到Command、AggregateRoot、Event、EventStorage、ReportDatabase的基礎知識,了解他們相互關係,尤其是如何進行事件存儲、如何進行事件回放的內容。

另外,我們發現在使用CQRS+ES的過程中,項目的複雜度增加了很多,我們不可避免的要使用EventStore、Messaging等架構,從而影響那些不了解CQRS的團隊成員的加入,因此在應用到實際項目的時候,要適可而止,慎重選擇,避免過度設計。

由於這是一個示例,項目代碼中存在很多不夠嚴謹的地方,大家在學習的過程中應進行甄別。

由於本人的知識有限,如果內容中存在不準確或錯誤的地方,還請不吝賜教!

【精選推薦文章】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

Colder框架硬核更新(Sharding+IOC)

目錄

  • 引言
  • 控制反轉
  • 讀寫分離分庫分表
    • 理論基礎
    • 設計目標
    • 現狀調研
    • 設計思路
    • 實現之過五關斬六將
      • 動態對象
      • 動態模型緩存
      • 數據源移植
      • 查詢表達式樹深度移植
      • 數據合併算法
      • 事務支持
    • 實際使用
  • 展望未來

引言

前方硬核警告:全文乾貨11000+字,請耐心閱讀
遙想去年這個時候,差不多剛剛畢業,如今正式工作差不多一年了。Colder開源快速開發框架從上次版本發布至今差不多有三個月了,Github的星星5個版本框架總共也有近800顆,QQ群從最初的一個人發展到現在的500人(吐槽下,人數上限了,太窮開不起SVIP,所以另開了一個,群號在文章末),這都是大家共同發展的結果,本框架能夠幫助到大家鄙人就十分開心。但是,技術是不斷髮展的,本框架也必須適應潮流,不斷升級才能夠與時俱進,在實際意義上提高生產力。本系列框架從原始雛形(鄙人畢業設計)=>.NET45+Easyui=>.NET Core2.1+Easyui=>.NET45+AdminLTE=>.NET Core2.1+AdminLTE,這其中都是根據實際情況不斷升級。例如鄙人最初的畢業設計搭建了框架的雛形(倉儲層不夠完善、界面較簡陋),並不適合實際的生產開發,因此使用Easyui作為前端UI框架(控件豐富,使用簡單),后又由於.NET Core的發展迅速,已經發展到2.0,其基礎類庫組件也相對比較成熟了,因此從.NET45遷移到.NET Core。後來發現Easyui的樣式比較落後,給人一種過時古老的感覺,故而又將前端UI改為基於Bootstrap的AdminLTE,比較成熟主流並且開源。
但是,新的要求又出現了:

  • 由於沒有使用IOC導致各個類通過New導致的強耦合問題
  • 數據庫大數據量如何處理的問題
    因此,本次版本更新主要就是為了解決上述的問題,即全面使用Autofac作為IOC容器實現解耦以及數據庫讀寫分離分庫分表(Sharding)支持。下面將分別介紹。
    這次更新.NET45版本與.NET Core版本同步更新:
.NET版本 前端UI 地址
Core2.2 AdminLTE https://github.com/Coldairarrow/Colder.Fx.Core.AdminLTE
.NET4.52 AdminLTE https://github.com/Coldairarrow/Colder.Fx.Net.AdminLTE

控制反轉

IOC(DI),即控制反轉(依賴注入),相關概念大家應該都知道,並且大多數人應該都已經運用於實際。我就簡單描述下,簡單講就是面向接口編程,通過接口來解除類之間的強耦合,方便開發維護測試。這個概念在JAVA開發中應該比較普遍,因為有Spring框架的正確引導,但是在.NET中可能開發人員的相關意識就沒那麼強,JAVA與.NET我這裏不做評價,但是作為技術人員,天生就是不斷學習的,好的東西當然要學習,畢竟技多不壓身。

在.NET 領域中IOC框架主流有兩個,即Autofac與Unity,這兩個都是優秀的開源框架,經過一番考量后我最終選擇了更加主流的(星星更多)Autofac。

關於Autofac的詳細使用教程請看官方文檔https://autofac.org/,我這裏主要介紹下集成到本框架的思路以及用法。
傳統使用方法通過手動註冊具體的類實現某接口,這種做法顯然不符合實際生產需求,需要一種自動註冊的方式。本框架通過定義兩個接口類:IDependency與ICircleDependency來作為依賴注入標記,所有需要使用IOC的類只需要繼承其中一個接口就好了,其中IDependency是普通注入標記,支持屬性注入但不支持循環依賴,ICircleDependency是循環依賴注入標記,支持循環依賴,實際使用中按需選擇即可。下面代碼就是自動註冊的實現:

var builder = new ContainerBuilder();

var baseType = typeof(IDependency);
var baseTypeCircle = typeof(ICircleDependency);

//Coldairarrow相關程序集
var assemblys = BuildManager.GetReferencedAssemblies().Cast<Assembly>()
    .Where(x => x.FullName.Contains("Coldairarrow")).ToList();

//自動注入IDependency接口,支持AOP
builder.RegisterAssemblyTypes(assemblys.ToArray())
    .Where(x => baseType.IsAssignableFrom(x) && x != baseType)
    .AsImplementedInterfaces()
    .PropertiesAutowired()
    .InstancePerLifetimeScope()
    .EnableInterfaceInterceptors()
    .InterceptedBy(typeof(Interceptor));

//自動注入ICircleDependency接口,循環依賴注入,不支持AOP
builder.RegisterAssemblyTypes(assemblys.ToArray())
    .Where(x => baseTypeCircle.IsAssignableFrom(x) && x != baseTypeCircle)
    .AsImplementedInterfaces()
    .PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies)
    .InstancePerLifetimeScope();

//註冊Controller
builder.RegisterControllers(assemblys.ToArray())
    .PropertiesAutowired();

//註冊Filter
builder.RegisterFilterProvider();

//註冊View
builder.RegisterSource(new ViewRegistrationSource());

//AOP
builder.RegisterType<Interceptor>();

var container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));

AutofacHelper.Container = container;

代碼中有相關註釋,使用方法推薦使用構造函數注入:

框架已在Business層與Web層全面使用DI,Util層、DataRepository層與Entity層不涉及業務邏輯,因此不使用DI。

讀寫分離分庫分表

前面的IOC或許沒啥可驚喜的,但是數據庫讀寫分離分庫分表應該不會讓大家失望。接下來將闡述下框架支持Sharding的設計思路以及具體使用方法。

理論基礎

數據庫讀寫分離分庫分表(以下簡稱Sharding),這並不是什麼新概念,網上也有許多的相關資料。其根本就是為了解決一個問題,即數據庫大數據量如何處理的問題。

當業務規模較小時,使用一個數據庫即可滿足,但是當業務規模不斷擴大(數據量增大、用戶數增多),數據庫最終將會成為瓶頸(響應慢)。數據庫瓶頸主要有三種情況:數據量不大但是讀寫頻繁數據量大但是讀寫不頻繁以及數據量大並且讀寫頻繁

首先,為了解決數據量不大但是讀寫頻繁導致的瓶頸,需要使用讀寫分離,所謂讀寫分離就是將單一的數據庫分為多個數據庫,一些數據庫作為寫庫(主庫),一些數據庫作為讀庫(從庫),並且開啟主從複製(實時將寫入的數據同步到從庫中),這樣將數據的讀寫分離后,將原來單一數據庫用戶的讀寫操作分散到多個數據庫中,極大的降低了數據庫壓力,並且打多數情況下讀操作要遠多於寫操作,因此實際運用中大多使用一主多從的模式。

其次,為了解決數據量大但是讀寫不頻繁導致的瓶頸,需要使用分庫分表。其實思想也是一樣的,即分而治之,一切複雜系統都是通過合理的拆分從而有效的解決問題。分庫分表就是將原來的單一數據庫拆分為多個數據庫,將原來的一張表拆分為多張表,這樣表的數據量就將下來了,從而解決問題。但是,拆表並不是胡亂拆的,隨便拆到時候數據都找不到,那還怎麼玩,因此拆表需要按照一定的規則來進行。最簡單的拆表規則,就是根據Id字段Hash后求余,這種方式使用簡單但是擴容很麻煩(絕大多數都需要遷移,工作量巨大,十分麻煩),因此大多用於基本無需擴容的業務場景。後來經過一番研究后,發現可以使用雪花Id(分佈式自增Id)來解決問題,雪花Id中自帶了時間軸,因此在擴容時可以根據時間段來判斷具體的分片規則,從而擴容時無需數據遷移,但是存在一定程度上的數據熱點問題。最後,找到了葵花寶典-一致性哈希,關於一致性哈希的理論我這裏就不獻醜了,相關資料網上一大把。一致性哈希從一定程度上解決了普通哈希的擴容問題與數據熱點問題,框架也支持使用一致性哈希分片規則。

最後,就是大BOSS,大數據量與大訪問量,很簡單隻需要結合讀寫分離與分庫分表即可,下錶是具體業務場景與採用方案的關係
| 數據量\訪問量 | | |
|-|-|-|
|| 無| 讀寫分離 |
| | 分庫分表 |讀寫分離分庫分表|

設計目標

首先定一個小目標(先賺他一個億):支持多種數據庫,使用簡單,業務升級改動小。
有了目標就需要調查業界情況,實現Sharding,市面上主要分為兩種,即使用中間件與客戶端實現。

現狀調研

中間件的優點是對客戶端透明,即對於客戶端來講中間件就是數據庫,因此對於業務改動影響幾乎沒有,但是對中間件的要求就很高,目前市面上比較主流成熟的就是mycat,其對MySQL支持比較好,但是對於其他數據庫支持就比較無力(個人測試,沒有深入研究,若有不妥請不要糾結),並且不支持EF,此方案行不通。其它類型數據庫也有對應的中間件,但是都並不如意,自己開發更不現實,因此使用中間件方案行不通。

既然中間件行不通,那就只能選擇客戶端方案了。目前在JAVA中有大名鼎鼎的Sharding-JDBC,了解了下貌似很牛逼,可惜.NET中並沒有Sharding-NET,但是有FreeSql,粗略了解了下是一個比較強大ORM框架,但我的框架原來底層是使用EF的,並且EF是.NET中主流的ORM框架,整體遷移到FreeSql不現實,因此最終沒找到成熟的解決方案。

設計思路

最後終於到了最壞的情況,既沒有完美的中間件方案,又沒有現成的客戶端方案,怎麼辦呢?放棄是不可能的,這輩子都不可能放棄的,終於,內心受到了黨的啟發,決定另起爐灶(既然沒有現成的那就自己早造)、打掃乾淨屋子再請客(重構數據倉儲層,實現Sharding)、一邊倒(堅定目標不改變,不妥協),由於EF支持多種數據庫,已經對底層SQL進行了抽象封裝,因此決定基於EF打造一套讀寫分離分庫分表方案。

數據庫讀寫分離實現:讀寫分離比較簡單,在倉儲接口中已經明確定義了CRUD操作接口,其中增、刪、改就是指寫操作,寫的時候按照具體的讀寫規則找到具體的寫庫進行寫操作即可,讀操作(查數據)按照具體的讀規則找到具體的讀庫進行讀即可。

數據庫分庫分表:分庫還好說,使用不同的數據庫即可,分表就比較麻煩了。首先實現分表的寫操作,可以根據分片規則能夠找到具體的物理表然後進行操作即可,實現比較容易。然後實現分表的讀操作,這個就比較麻煩了,就好比前面的都是斗皇以下的在小打小鬧,而這個卻是斗帝(騎馬),但是,作為一名合格的攻城獅是不怕斗帝的,遇到了困難不要慌,需要冷靜思考處理。前面提到過,解決複雜問題就是一個字“”,首先聯表查詢就直接不考慮支持了(大數據量進行笛卡爾積就是一種愚蠢的做法,怎麼優化都沒用,物理數據庫隔絕聯表不現實,實現難度太大放棄)。接下來考慮最常用的方法:分頁查詢、數據篩選、最大值、最小值、平均值、數據量統計,EF中查詢都是通過IQueryable接口實現的,IQueryable中主要包括了數據源(特定表)與關聯的表達式樹Expression,通過考慮將數據源與關聯的表達式樹移植到分表的IQueryable即可實現與抽象表相同的查詢語句,最後將併發多線程查詢分表的數據通過合併算法即可得到最終的實際數據。想法很美好,現實很殘酷,下面為大家簡單闡述下實現過程,可以說是過五關斬六將

實現之過五關斬六將

動態對象

首先考慮分表的寫操作,傳統用法都有具體的實體類型進行操作,但是分表時,例如Base_UnitTest_0、Base_UnitTest_1、Base_UnitTest_2,這些表全部保存為實體類不現實,因此需要一種非泛型方法,後來在EF的關鍵類DbContext中找到DbEntityEntry Entry(object entity)方法,通過DbEntityEntry可以實現數據的增刪改操作,又注意到傳入參數是object,由此猜測EF支持非泛型操作,即只需要傳入特定類型的object對象也行。例如抽象表是Base_UnitTest,實際需要映射到表Base_UnitTest_0,那麼怎樣將Base_UnitTest類型的對象轉換成Base_UnitTest_0類型的對象?經過查閱資料,可以通過System.Reflection.Emit命名空間下的TypeBuilder在運行時創建動態類型,即可以在運行時創建Base_UnitTest_0類型,該類型擁有與Base_UnitTest完全一樣的屬性(因為表結構完全一樣),創建了需要的類型,接下來只需要通過Json.NET將Base_UnitTest對象轉為Base_UnitTest_0即可。實現到這裏,原以為會順利成功,但是並沒有那麼簡單,EF直接報錯“上下文不包含模型Base_UnitTest_0”,這明顯就是模型的問題了,接下來進入下一關:EF動態模型緩存

動態模型緩存

通常都是通過繼承DbContext重寫OnModelCreating方法來註冊實體模型,這裡有個坑就是OnModelCreating只會執行一次,並最終生成DbCompiledModel然後將其緩存,後續創建的DbContext就會直接使用緩存的DbCompiledModel,由於最初註冊實體模型的時候只有抽象類型Base_UnitTest,所有後續在使用Base_UnitTest_0對象的時候會報錯。為了解決這個問題,需要自己管理DbCompiledModel緩存,實現過程比較麻煩,這裏就不詳細分析了,有興趣的直接看源碼即可。將緩存問題解決后,終於成功的實現了Base_UnitTest_0的增刪改,這時,心裏一喜(有戲)。實現了寫操作(增、刪、改)之後,接下來就是實現查詢了,那麼如何實現查詢呢?EF中查詢操作都是通過IQueryable接口實現的,IQueryable中包括了具體數據表的數據源和關聯的查詢表達式樹,那麼如何將IQueryable < Base_UnitTest >轉換為IQueryable < Base_UnitTest_0 > 並且保留原始查詢語句就成了關鍵問題。

數據源移植

根據經驗,想一舉同時移植數據源與表達式樹應該不現實,實際情況也是如此,移植數據源,通過使用ExpressionVisitor可以找到根數據源,其實是一個ObjectQuery類型,並且在表達式樹中是以ConstantExpression存在,同樣通過ExpressionVisitor則可將原ObjectQuery替換為新的,實現過程省略10000字。

查詢表達式樹深度移植

數據源移植后,別以為就大功告成了,接下來進入一個深坑(最難點),表達式樹移植,經過一番踩坑后發現,表達式樹中的所有節點都是樹狀結構,任何一個查詢(Where、OrderBy、Skip、Take等)在表達式樹中都是以一個節點存在,並且一級扣一級,也就是說你改了數據源沒用,因為數據源只是表達式樹的根節點,下面的所有子節點還都是原來的根節點發的牙,並不能使用,那怎樣才能用新數據源構建與原數據源一樣的表達式樹呢?經過如下分析:IQuryable中的所有操作都是MethodCallExpression一層一層包裹,那麼我從外到內剝開方法,然後再從內到外包裹新的數據源,那不就模擬得一模一樣了嗎?(貌似有戲),想到先進后出腦子里直接就蹦出了數據結構中的,強大的.NET當然支持棧了,經過一番操作(奮鬥幾個晚上),此處省略10000字,最終完成IQueryable的移植,即從IQueryable < Base_UnitTest >轉換為IQueryable < Base_UnitTest_0 > 並且保留原始查詢語句。有了分表的IQueryable就能夠獲取分表的數據了,最後需要將獲取的分表數據進行合併。

數據合併算法

分表后的數據合併算法主要參考了網上的一些資料,雖然分庫分表的實現方式各不相同,但是思想都是差不多的,例如需要獲取Count,只需要將各個分表的Count求和即可,最大值只需要所有分表的最大值的最大值即可,最小值只需要所有分表最小值的最小值即可,平均值需要所有分表的和然後除以所有分表的數據條數即可。最後比較麻煩的就是分頁查詢,分頁查詢需要分表排序后獲取前N頁的所有數據(不能直接獲取某一頁的數據,因為不一定就是那一頁),最後將所有表的數據再進行分頁即可。實現到這裏,已經實現了增、刪、改、查了,看似革命已經成功,其實還有最後的大BOSS:事務支持

事務支持

因為分表很可能不在同一個數據庫中,因為普通的單庫事務顯然不能滿足需求,原本框架中已經有分佈式事務支持(多庫事務),這裏需要集成到Sharding中,實現過程省略10000字,最終黃天不負有心人終於實現了。

到這裏,肯定有暴躁老哥坐不住了:你前面BBB那麼多,說得那麼牛逼,到底怎麼用啊???,若文章到此為止,估計就是下圖:

鄙人則回復如下:

深夜12點了,放鬆一下,最後介紹如何使用

實際使用

本框架支持數據庫讀寫分離分庫分表(即Sharding),並且支持主流關係型數據庫(SQLServer、Oracle、MySQL、PostgreSQL),理論上只要EF支持那麼本框架支持。
由於技術原因以及結合實際情況,目前本框架僅支持單表的Sharding,即支持單表的CRUD、分頁、統計(數量、最大值、最小值、平均值),支持跨庫(表分散在不同的數據庫中,不同類型數據庫也支持)。具體如何使用如下:

  • Sharding配置
    首先、要進行分庫分表操作,那麼必要的配置必不可少。配置代碼如下:
ShardingConfigBootstrapper.Bootstrap()
    //添加數據源
    .AddDataSource("BaseDb", DatabaseType.SqlServer, dbBuilder =>
    {
        //添加物理數據庫
        dbBuilder.AddPhsicDb("BaseDb", ReadWriteType.ReadAndWrite);
    })
    //添加抽象數據庫
    .AddAbsDb("BaseDb", absTableBuilder =>
    {
        //添加抽象數據表
        absTableBuilder.AddAbsTable("Base_UnitTest", tableBuilder =>
        {
            //添加物理數據表
            tableBuilder.AddPhsicTable("Base_UnitTest_0", "BaseDb");
            tableBuilder.AddPhsicTable("Base_UnitTest_1", "BaseDb");
            tableBuilder.AddPhsicTable("Base_UnitTest_2", "BaseDb");
        }, new ModShardingRule("Base_UnitTest", "Id", 3));
    });

上述代碼中完成了Sharding的配置:
ShardingConfigBootstrapper.Bootstrap()在一個項目中只能執行一次,所以建議放到Application_Start中(ASP.NET Core中的Startup)
AddDataSource是指添加數據源,數據源可以看做抽象數據庫,一個數據源包含了一組同類型的物理數據庫,即實際的數據庫。一個數據源至少包含一個物理數據庫,多個物理數據庫需要開啟主從複製或主主複製,通過ReadWriteType(寫、讀、寫和讀)參數來指定數據庫的操作類型,通常將寫庫作為主庫,讀庫作為從庫。同一個數據源中的物理數據庫類型相同,表結構也相同。
配置好數據源后就可以通過AddAbsDb來添加抽象數據庫,抽象數據庫中需要添加抽象數據表。如上抽象表Base_UnitTest對應的物理表就是Base_UnitTest_0、Base_UnitTest_1與Base_UnitTest_2,並且這三張表都屬於數據源BaseDb。分表配置當然需要分表規則(即通過一種規則找到具體數據在哪張表中)。
上述代碼中使用了最簡單的取模分片規則
源碼如下:

可以看到其使用方式及優缺點。
另外還有一致性HASH分片規則

雪花Id的mod分片規則

上述的分片規則各有優劣,都實現IShardingRule接口,實際上只需要實現FindTable方法即可實現自定義分片規則。
實際使用中個人推薦使用雪花Id的mod分片規,這也是為什麼前面數據庫設計規範中默認使用雪花Id作為數據庫主鍵的原因(PS,之前版本使用GUID作為主鍵被各種嫌棄,這次看你們怎麼說)

  • 使用方式
    配置完成,下面開始使用,使用方式非常簡單,與平常使用基本一致
    首先獲取分片倉儲接口IShardingRepository
IShardingRepository _db = DbFactory.GetRepository().ToSharding();

然後即可進行數據操作:

Base_UnitTest _newData  = new Base_UnitTest
{
    Id = Guid.NewGuid().ToString(),
    UserId = "Admin",
    UserName = "超級管理員",
    Age = 22
};
List<Base_UnitTest> _insertList = new List<Base_UnitTest>
{
    new Base_UnitTest
    {
        Id = Guid.NewGuid().ToString(),
        UserId = "Admin1",
        UserName = "超級管理員1",
        Age = 22
    },
    new Base_UnitTest
    {
        Id = Guid.NewGuid().ToString(),
        UserId = "Admin2",
        UserName = "超級管理員2",
        Age = 22
    }
};
//添加單條數據
_db.Insert(_newData);
//添加多條數據
_db.Insert(_insertList);
//清空表
_db.DeleteAll<Base_UnitTest>();
//刪除單條數據
_db.Delete(_newData);
//刪除多條數據
_db.Delete(_insertList);
//刪除指定數據
_db.Delete<Base_UnitTest>(x => x.UserId == "Admin2");
//更新單條數據
_db.Update(_newData);
//更新多條數據
_db.Update(_insertList);
//更新單條數據指定屬性
_db.UpdateAny(_newData, new List<string> { "UserName", "Age" });
//更新多條數據指定屬性
_db.UpdateAny(_insertList, new List<string> { "UserName", "Age" });
//更新指定條件數據
_db.UpdateWhere<Base_UnitTest>(x => x.UserId == "Admin", x =>
{
    x.UserId = "Admin2";
});
//GetList獲取表的所有數據
var list=_db.GetList<Base_UnitTest>();
//GetIQPagination獲取分頁后的數據
var list=_db.GetIShardingQueryable<Base_UnitTest>().GetPagination(pagination);
//Max
var max=_db.GetIShardingQueryable<Base_UnitTest>().Max(x => x.Age);
//Min
var min=_db.GetIShardingQueryable<Base_UnitTest>().Min(x => x.Age);
//Average
var min=_db.GetIShardingQueryable<Base_UnitTest>().Average(x => x.Age);
//Count
var min=_db.GetIShardingQueryable<Base_UnitTest>().Count();
//事務,使用方式與普通事務一致
using (var transaction = _db.BeginTransaction())
{
    _db.Insert(_newData);
    var newData2 = _newData.DeepClone();
    _db.Insert(newData2);
    bool succcess = _db.EndTransaction().Success;
}

上述操作中表面上是操作Base_UnitTest表,實際上卻在按照一定規則使用Base_UnitTest_0~2三張表,使分片對業務操作透明,極大提高開發效率,基本達成了最初定製的小目標。
具體使用方式請參考單元測試源碼:
“\src\Coldairarrow.UnitTests\DataRepository\ShardingTest.cs”

最後放上簡單的測試圖:300W的表分成三張100W的表後效果

看來功夫沒白費,效果明顯(還不快點贊

展望未來

結束也是是新的開始,版本後續計劃採用前後端完全分離方案,前端使用vue-element-admin,後端以.NET Core為主,傳統的.NET將逐步停止更新,敬請期待!
文章雖然結束了,但是技術永無止境,希望我的文檔能夠幫助到大家。
深夜碼字,實屬不易,文章中難免會出現一些紕漏,一些觀點也不一定完全正確,還望各位大哥不吝賜教。
最後覺得文檔不錯,請點贊,Github請星星,若有各種疑問歡迎進群交流:
QQ群1:373144077(已滿)
QQ群2:579202910

See You

【精選推薦文章】

智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

想知道網站建置、網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計及後台網頁設計

帶您來看台北網站建置台北網頁設計,各種案例分享

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

Java基礎(六) static五大應用場景

static和final是兩個我們必須掌握的關鍵字。不同於其他關鍵字,他們都有多種用法,而且在一定環境下使用,可以提高程序的運行性能,優化程序的結構。上一個章節我們講了final關鍵字的原理及用法,本章節我們來了解一下static關鍵字原理及其用法。

一. static特點

static是一個修飾符,通常用於修飾變量和方法,如開發過程中用到的字典類數據都會用到static來修飾,工具類方法,如Dateutils,Stringutils這類工具方法也會用到static來修飾,那麼除了這兩種最常用的場景外,是否還有其他場景呢,答案是:有的,總共五種:

  1. static變量
  2. static方法
  3. static代碼塊
  4. static內部類
  5. static包內導入

static修飾的變量、方法、代碼塊、內部類在類加載期間就已經完成初始化,存儲在Java Heap(JDK7.0之前存儲在方法區)中靜態存儲區,因此static優於對象而存在。

static修飾的成員(變量、方法)被所有對象所共享,也叫靜態變量或靜態方法,可直接通過類調用(也建議通過類調用)。

二. static 變量

static變量隨着類的加載而存在,隨着類的消失而消失,當類被加載時,就會為靜態變量在Java Heap中分配內存空間,可以通過【類.變量名】和【對象.變量名】的方式調用,建議直接使用【類.變量名】的方式,

public class Person {
    private String name;

    private static int eyeNum;

    public static int legNum = 2;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public static int getEyeNum() {
        return eyeNum;
    }

    public static void setEyeNum(int eyeNum) {
        Person.eyeNum = eyeNum;
    }
}
public static void main(String[] args) {
    Person person = new Person();
    person.setEyeNum(25);

    Person person1 = new Person();
    person1.setEyeNum(28);
    System.out.println(person.getEyeNum());//28
    System.out.println(person1.getEyeNum());//28
    
    int legNum = person.legNum;
    System.out.println(legNum);//2
}

從上面的例子可以看出靜態變量是對所有對象共享,一個對象對其值的改動,直接就會造成另一個對象取值的不同。

什麼時候使用static變量?

作為共享變量使用,通常搭配final關鍵字一起使用,比如我們常用的字典類數據;

private static final String GENERAL_MAN = "man";

減少對象的創建,比如在類開頭的部分,定義Logger方法,用於異常日誌採集

private static Logger LOGGER = LogFactory.getLoggger(MyClass.class);

始終返回同一變量,比如我們的單例模式。

三. static 方法

靜態方法只能訪問靜態成員(靜態變量、靜態方法),而非靜態方法既可訪問靜態方法也可訪問非靜態方法;因為靜態成員優於對象而存在,因此無法調用和對象相關的關鍵字,如this,super,無法通過關鍵字訪問對象資源。

public class Person {
    private String name;    
    private static int eyeNum;    
    public static int legNum = 2;    
    public String getName() {
        return name;    
    }    
    public void setName(String name) {
        this.name = name;    
    }    
    public static int getEyeNum() {
        System.out.println(name);//編譯出錯,name不可用
        return eyeNum;
    }    
    public static void setEyeNum(int eyeNum) {
        Person.eyeNum = eyeNum;        
        this.name = "";//編譯出錯,this不可用
    }
}

什麼時候使用static方法?

static方法一般用於與當前對象無法的工廠方法、工具方法。如Math.sqrt(),Arrays.sort(),StringUtils.isEmpty()等。

四. static 代碼塊

static代碼塊相對於static變量和static方法來說使用不是那麼廣泛,但也算是比較常見的用法了,static代碼塊在加載一個類的時候最先執行,且只執行一次。

public static Map<String, String> timeTypes;
static {
    timeTypes = new HashMap<>();
    timeTypes.put("year", "年");
    timeTypes.put("quarter", "季");
    timeTypes.put("month", "月");
    timeTypes.put("day", "日");
    System.out.println("初始化1");
}
public static void main(String[] args) {
    System.out.println("初始化2");
}

執行結果是:

初始化1;

初始化2;

什麼時候使用static代碼塊?

一般在進行初始化操作時,比如讀取配置文件信息,獲取當前服務器參數等

五. static內部類

定義一個內部類,加上static,就成為了一個static內部類,static只能修飾內部類,不能修飾頂級類,靜態內部類在業務應用系統開發中使用的不多。

public class StaticCouter {
    private String str0 = "hi";    //非靜態變量    
    private static String str1 = "hello";  //靜態變量   
    static class StaticInnerClass{//靜態內部類
        public void getMessage(){
            System.out.println(str0);//編譯出錯
            System.out.println(str1);
        }
    }    
    class NormalInnerClass{//非靜態內部類
        public void getMessage(){
            System.out.println(str0);
            System.out.println(str1);
        }
    }
}

靜態內部類與非靜態內部類有何異同?

靜態內部類 非靜態內部類
不需要有指向外部類的引用 必須通過外部類的new關鍵字引用
可定義普通變量和方法,也可定義靜態變量和方法 可定義普通變量和方法,不可定義靜態變量和方法
可以調用外部類的靜態成員,不能調用外部類的普通成員 可調用外部類的普通成員和靜態成員
public static void main(String[] args) {
    //創建靜態內部類實例    
    StaticInnerClass staticInnerClass = new StaticInnerClass();    
    //調用靜態內部類方法    
    staticInnerClass.getMessage();    
    //創建靜態內部類實例    
    StaticCouter.StaticInnerClass staticInnerClass1 = new staticCouter.StaticInnerClass();    
    //調用靜態內部類方法
    staticInnerClass1.getMessage();
    //創建普通內部類實例
    StaticCouter.NormalInnerClass normalInnerClass = new StaticCouter().new NormalInnerClass();
    //調用普通內部類方法
    normalInnerClass.getMessage();
}

六. static包內導入

這個概念不太好理解,舉個例子

public static void main(String[] args) {
    int[] arra = {1,4,5,7};
    Arrays.sort(arra);
    Arrays.asList(arra);
    Arrays.fill(arra, 6);
}

static包導入目的就是去掉重複的Arrays類名調用

通過在頂部引入

import static java.util.Arrays.*

即可把Arrays類中所有的靜態變量,方法,內部類等都引入當前類中,調用時直接調用sort(arra),asList(arra),

java5后引入的,不常用,調用類方法時會比較簡單,但可讀性不好,慎用。

七. 總結

static是java中很常用的一個關鍵字,使用場景也很多,本文主要介紹了它的五種用法,static變量,static方法,static代碼塊,static內部類,static包內導入,若有不對之處,請批評指正,望共同進步,謝謝!

【精選推薦文章】

如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

想要讓你的商品在網路上成為最夯、最多人討論的話題?

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

不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師"嚨底家"!!

騰訊雲重磅發布系列自研產品,自研服務器星星海震撼亮相

  10 月 29 日,在騰訊全球数字生態大會成都峰會上,騰訊雲重磅發布系列自研產品,包括騰訊自研第四代數據中心T-block 產品家族、第一款真正為雲而生的自研服務器“星星海”等基礎產品,結合現場發布的彈性容器服務、無服務器等自研產品,騰訊雲正在加速構建從底層到應用層的自研技術體系,全方位助力各行業数字化、智能化升級。


騰訊服務器供應鏈總經理劉裕勛

  騰訊服務器供應鏈總經理劉裕勛表示,“作為全球 IaaS 市場增長最快的雲計算廠商,騰訊全網服務器總量已經超過 110 萬台。業務規模的急劇增長,對基礎設施架構、運營成本等提出新的挑戰。在這種背景下, 騰訊雲圍繞“降本增效”的核心訴求,不斷整合自身資源與經驗,加速構建從基礎設施層到應用層的全鏈路自研技術體系,為下一波雲計算變革做好人才、技術和生態層面的儲備。

  騰訊第一款!自研服務器星星海真正“為雲而生”

  騰訊雲這次對外發布的深度自研“星星海”服務器,是騰訊第一款真正為雲而生的服務器。

  在設計過程中,結合數據中心實際部署環境要求,針對雲端的計算、存儲,網絡等場景做了重點優化,能夠有效滿足騰訊雲 98% 應用場景需求。同時,通過精細的硬件設計大幅度降低硬件成本和運營成本,有效匹配騰訊服務器供應鏈整體升級,更好的整合硬件產業鏈能力,為海量用戶提供行業最新技術能力,享受新技術紅利。


騰訊自研服務器星星海

  和傳統服務器相比,星星海統一的整機方案可以支持不同的 CPU 主機,前瞻性的高兼容架構,統一規劃的硬件底座,可以支持未來3-5 年的服務器產品演進。

  另外,聯合 AMD,在其最新 rome 平台下深度定製 CPU,實現行業最優單核性能和最優單核 TCO;根據測試,星星海可以實現雲服務實例綜合性能提升 35% 以上,其中視頻處理速度提升 40%,圖形轉碼得分提升 35%,Web 服務頁面 QPS 提升高達 152%。先進的熱虹吸管散熱技術實現最大負載能效對比業界可提高 50%。同時,基於可信鏈傳遞的硬件防篡改能力,使得服務器具備符合大規模雲應用的硬件安全能力。皮實可靠的工程設計與前瞻的高兼容架構結合,有效提高了服務器的可靠性、可供應性與可拓展性。

  隨同星星海服務器一同推出的還有騰訊第四代數據中心T-block 產品家族,作為自研體系中的一大“明星產品”,T-block 最大的優勢就是讓用戶能夠以搭積木的方式,快速實現全數據中心的模塊化配置及建設。

  相比於傳統大規模數據中心,T-block 顯著減少了建設周期和施工成本。同時,由於率先在行業內採用更高效率的製冷和供配電架構,系統能源使用效率得到有效提升。以一個擁有 30 萬台服務器的園區為例,一年可節省 2.5 億度電,大概相當於成都市 1 天的全社會用電量,真正實現綠色數據中心理念。另外,T-block 家族中的典型產品 Mini T-block 則更加輕量化,對場地的要求更低,企業可以像使用U盤一樣可以“即插即用”,大幅降低了數據中心的准入門檻和投入成本。在此基礎上,再配合騰訊智維對數據中心進行智能運維管理、以及基於騰訊覓蹤實現智能安防,助力企業在全面提高數據中心運行效率及運行可靠性的同時降低成本投入。

  計算、存儲、數據庫多款自研產品亮相,基礎設施性能全面升級

  除了上面發布的系列硬件產品之外,在基礎設施層面,騰訊雲還在計算、存儲、數據庫等領域發布了一系列自研產品,包括大規模雲計算操作系統 VStation、自研百 EB 級存儲多 AZ 架構,以及自研高性能百 EB 級 Hadoop 文件系統 CHDFS,全線升級基礎設施性能,降低雲服務成本。

  追溯雲計算的發展,其最核心的問題是如何把成千上萬台機器高效地組織起來,靈活進行任務調度和管理,從而像使用單台機器一樣方便地使用多台機器。騰訊雲基於在基礎設施上多年深耕,自主研發了具有強大業界競爭力的雲計算操作系統 VStation。作為騰訊雲計算的核心,Vstation 已經在數百個數據中心和節點進行部署,並可支持數萬台虛擬機分鐘級別創建。基於 VStation,騰訊雲已經成為業界首家單集群調度 10 萬台服務器的廠商。

  騰訊雲自研百 EB 級存儲多 AZ 架構是對原有存儲引擎的一次重大升級,性能可達到 6 萬 QPS,單集群規模可達到 100 萬節點,處於業界領先水平;騰訊雲多 AZ 存儲架構能夠為用戶數據提供了數據中心級別的容災能力。 針對雲端的大數據分析場景,新發布的雲 HDFS(CHDFS)提供標準 HDFS 訪問協議,客戶無需更改現有代碼,即可使用高性能、高可靠、高可用、多維度安全的分佈式文件系統。並且,隨着業務需求的變化,存儲空間無上限,滿足海量大數據存儲與分析業務需求。在大數據分析場景下,CHDFS 還可以結合騰訊雲彈性 MapReduce (EMR),實現大數據計算與存儲分離,降低成本。

  騰訊雲自研的分佈式國產數據庫 TBase,提供行列混合的存儲模式既支持 OLTP 又支持 OLAP。同時,在提供 NewSQL 便利性的同時完整支持分佈式事務並保持 SQL 兼容性,支持 RR、RC、SSI 三種隔離級別,兼容 Oracle 語法。TBase 具備強大的安全和容災能力,已經成功應用在騰訊內部的微信支付,以及外部眾多金融、政府、電信、醫療等行業的核心業務系統。

  此外,騰訊雲還在大會現場宣布,與微信聯合推出的小程序·雲開發目前已經服務 50 萬開發者,超過 23 萬+小程序賬號開通小程序·雲開發。在基礎能力上,“小程序雲開發”正在進一步繼續深度整合微信生態能力,新增支付訂閱消息、客服消息等,即將支持微信支付,在未來,小程序·雲開發將面向企業服務,打造雲開發的後端服務市場。生態上,將繼續落實 10 億扶持計劃和小程序加速器計劃,從資源、品牌、賦能、商機四個維度,真金白銀,加速服務商成長。

  聚焦開發者效率,騰訊雲加速構建自研技術體系

  除了基礎設施性能的提升,騰訊雲還聚焦開發者效率的變革,在平台層,不斷優化產品性能,加速構建自身的自研技術體系。

  比如,在容器領域,騰訊雲首次推出了無須用戶管理計算節點、完全兼容 kuberneters 社區管理方式的彈性容器服務。基於騰訊雲成熟的虛擬化和網絡技術,保證租戶間容器、容器網絡的安全隔離,為用戶帶來雲原生時代使用雲計算的新體驗。

  微服務領域,在之前微服務平台 TSF 的基礎上,騰訊雲基於無服務器技術,推出 TSF Serverless, 將無服務器架構和微服務平台完美融合,為開發者更高效可靠地提供一站式分佈式系統開發能力。

  此外,騰訊雲還通過一站式 DevOps 平台 CODING DevOps,為開發者提供優質穩定的研發管理工具,實踐敏捷開發與 DevOps,全面提升軟件交付質量與速度。

  自主創新是雲計算的核心生命力。從自研服務器到自研數據中心,再到自研雲端軟件,騰訊雲已經在雲計算技術自研領域進行了廣泛布局。未來,依託大量的技術積累與海量用戶服務經驗,繼續加速構建自研技術體系,為各行各業提供更優質的雲計算服務。

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

【精選推薦文章】

帶您來了解什麼是 USB CONNECTOR  ?

為什麼 USB CONNECTOR 是電子產業重要的元件?

又掌控什麼技術要點? 帶您認識其相關發展及效能

地平線發布旭日二代邊緣AI芯片,將算法演進趨勢融入BPU架構

  【新智元導讀】剛剛,地平線發布了最新的旭日二代邊緣 AI 芯片及全場景芯片解決方案。芯片集成地平線第二代 BPU 架構,同時將算法演進趨勢融入 BPU 架構。

  8 月 30 日,地平線投下一顆“重磅炸彈”:正式宣布量產中國首款車規級 AI 芯片——征程二代,成為攀登自動駕駛制高點的里程碑事件。

  兩個月不到,地平線再次宣布 AI 芯片新進展,於今日宣布推出新一代 AIoT 智能應用加速引擎——旭日二代邊緣 AI 芯片全場景芯片解決方案

  集成地平線第二代 BPU 架構,每秒處理超過 700 張圖片

  今天,地平線聯合創始人&技術副總裁黃暢發布並詳細解讀了旭日二代芯片:

  旭日二代是地平線面向未來物聯網推出的新一代智能應用加速引擎,也是地平線在自動駕駛芯片領域技術先發優勢的一次成功遷移

  旭日二代集成地平線第二代 BPU 架構(伯努利架構),典型算法模型算力利用率>90%;配合高效算法,每 TOPS 算力可處理的幀數高於標稱 4T 算力的 AI 芯片(>10 倍,同等算力 GPU)。

  針對物聯網場景下的主要目標群體“人”和“車”,旭日二代進行了大量的算法優化,在邊緣端即可實現全視頻結構化能力,高效靈活地處理多類 AI 任務,包括 10~30 萬人前端識別,密集人群時空屬性行為分析,多種非機動車、機動車檢測分類。

  旭日二代具備四大核心優勢

  • 高性能:等效算力 4Tops,可實時處理多類 AI 任務 
  • 低功耗:採用 28 HPC+ 低功耗 CMOS 工藝, 典型功耗 2 W
  • 開放工具鏈:可視化調試工具、豐富的算法樣例
  • 視頻全結構化能力:支持對多類人、車目標進行實時檢測和精準識別

  實際測試表明,旭日二代在端上高效分類模型 MobileNet V2 上可每秒處理超過 700 張圖片,功耗僅為 2W;使用地平線工具鏈優化后的 yolo3(調整網絡結構重訓)在相近的精度下,旭日二代每秒可以處理超過 40 張圖片(是業內標稱 4TOPS AI 芯片運行公版 yolo3 的近兩倍性能)。

  旭日二代不僅性能強大,而且好用易用,主要體現在:

  • 豐富的模型和應用示例
  • 支持 Mxnet 和 Tensorflow,2020 年初支持 pytorch 3
  • 可視化的性能分析工具
  • 快速上手的 BPU API,高度靈活的 HR Runtime API 和 Platform API,客戶可以根據需求選擇

  算法芯片協同優化,將算法演進趨勢融入 BPU 架構

  相比於單純做 AI 芯片硬件的公司和單純做 AI 軟件算法及應用的公司,地平線的獨特性在於,從創立之初就堅持走“算法+芯片”的軟硬結合道路,現如今,這已經成為科技行業的重要趨勢。

  黃暢曾在以“AI 元力,重啟未來”為主題的 AI World 2019 世界人工智能峰會上提到,“為實現軟硬件協同優化,必須判斷算法向前演進的趨勢,抓住市場最主流的算法確定芯片走向,但這不是追求單一的算法模型加速。”

  地平線提出“軟硬結合、開放賦能”,堅持算法與芯片協同優化,地平線創始人兼 CEO 余凱博士不止一次地對外表示未來 AI 競爭需要“軟硬結合”並形象的說:“如果只做硬件不做軟件,那給客戶交付的就是一塊石頭。

  此次,地平線也對重要應用場景中的關鍵算法發展趨勢進行預判,前瞻性地將其計算特點融入到計算架構的設計當中,使 AI 芯片隨着算法的演進趨勢,始終能夠保持相當高的有效利用率,從而真正意義上受益於算法創新帶來的優勢。


軟硬件協同優化,AI 效能靈活

  邊緣計算迎來全新構架變革,邊緣 AI 芯片成 AIoT 應用落地的引擎

  AI 計算可以分為雲計算、邊緣計算、端計算三個層次。他們在 AI 算力、實時性以及計算的通用性三個維度上,各有所長:

  • 雲計算面向的是最通用的計算,在所有的計算中,雲計算的時空範疇是最大的,多樣性最強,所需要的算力最高,但實時性較差,並且與場景相關性弱;雲端的數據種類也是最豐富的,可以橫跨多個維度,因而可以做複雜的認知計算和模型訓練。
  • 端計算是另外一個極端,與場景相關性最強,計算專用性非常強,追求極致效率,主要面向推理。
  • 處在中間的邊緣計算是新物種,它就像我們的脊椎一樣,連接我們的大腦(雲)和神經末梢(端)。其算力遠勝於端,同時對功耗的容忍度比端也強很多;相對於雲,其實時性更好,且可以結合具體場景進行特定優化。而 5G 技術的應用,可以大幅改善邊緣和端之間的數據帶寬和傳輸延遲,使得它能夠兼具雲和端的優勢,改變現有網絡互聯格局。

  5G 時代下的物聯網模式也將隨之而變,邊緣計算成為數據過濾器與控制閥,通過它的處理,可僅將低至萬分之一的有效數據上傳到雲端進行處理,大幅降低對於骨幹網的數據傳輸壓力。換句話說,5G 將會加快信息傳播速度,但卻無法從根本上解決數據節點的“堰塞湖”現象,這就為邊緣計算帶來了廣闊的發展空間

  在傳統互聯網時代,是端和雲的二元計算架構,數據持續向雲端轉移並被處理,端只是流量入口;但是邊緣計算的加入,帶來了新變量,它在端和雲之間,構造出一種全新的可能,其對於數據的控制力將帶來新的商業範式轉移,從技術角度講,邊緣計算具備了改造傳統互聯網計算架構的潛力,將帶來從軟件到硬件的全新構架變革

  AI on Horizon:圖像+語音,從像素集感知到時空語義建模

  地平線不斷加速邊緣 AI 芯片產品的更新迭代,以軟硬結合的一體化方案,帶來極致效能提升。基於邊緣 AI 芯片,地平線致力於打造一個多層次、多維度、多樣性的開放生態體系。

  專註邊緣計算,同時得益於算法和芯片協同優化,地平線此次面向 AIoT 客戶重點推出了以下解決方案: 

  • 地平線 HeroSpark 通行門禁考勤方案:整合 AI 芯片、算法、軟件及攝像頭模組,具備高性能、低功耗、快速集成等特點,方便客戶應用與快速落地量產,可打造具備高性能人臉識別技術的出入口門禁、 可視對講、閘道通行、辦公考勤等產品;
  •  地平線 HeroSights 智能攝像機解決方案:依託軟硬結合的邊緣 AI 芯片優勢,HeroSights 可以高性價比協助客戶實現產品形態快速落地,廣泛應用於零售、學校、工地、製造、物流、家居、交通、社區等各 AIoT 場景;
  •  地平線 Nebula 智能車載主動安全解決方案:該方案基於中國首款車規級 AI 芯片——征程二代打造, 是包括 ADAS、DMS、Face ID、語音等功能在內的全棧 AI 解決方案,具有高性能、低成本、接口靈活、支持多個硬件平台的特點,方便整車廠進行集成。

  基於旭日邊緣 AI 芯片,結合領先的深度學習算法,地平線可為客戶推出具備高性能、低功耗、快速集成等特點的智能物聯解決方案,賦能社區住宅、商業樓宇、園區、學校、工廠、企事業單位等場景。這與此次發布的一站式全場景邊緣 AI 芯片解決方案——Horizon Hero 密切相關。

  在地平線上海汽車展媒體發布活動上,余凱首次向公眾解讀了 AI on Horizon 的商業理念,他表示,“AI on Horizon,Journey Together”的理念就是:

  • 定位 Tier 2 供應商,只造武器不打仗,不碰數據,不做上層應用;
  • 芯片開放賦能,一路成就客戶;
  • 提供超高性價比,極致功耗和開放的服務。

  AI on Horizon 能力集主要是“圖像+語音”,從像素集感知,到時空語義建模。在二級結構化上,有語音識別、語音喚醒、唇語識別、聲紋識別、多模態情緒分析、活體檢測、Face-ID、人體 Re-ID、人體屬性分析;在三級結構化上,有多模態生物特徵識別、超大規模人臉識別、自動建檔、三維人體建模、跨攝像頭跟蹤、行為識別、稠密人群分析。

  BOOTPRINT X2 邊緣 AI 開發套件,降低開發者門檻

  在開發者生態方面,地平線通過工具鏈服務、與行業開放社區的形式降低開發者門檻,如地平線此次發布的 BOOTPRINT X2 邊緣 AI 開發套件,BOOTPRINT X2 邊緣 AI 開發套件是地平線加入 96Boards 社區,推出的基於 96Boards SOM 規範的邊緣 AI 開發套件。

  BOOTPRINT X2 邊緣 AI 開發套件主要具備以下功能:

  • 可支持圖像識別、檢測、分割等視覺處理功能以及離線自定義詞條等語音識別功能;
  • 接口豐富且支持介入公有雲;
  • 可滿足生態合作夥伴在智能物聯網、智能駕駛等領域的技術和原型開發、驗證評估等工作。

  開放賦能,提供 AIoT 芯片解決方案、增強語音抽取方案、邊緣 AI 智能攝像機方案等

  作為從邊緣計算中脫胎的邊緣 AI 芯片,截止目前,地平線“旭日系列”邊緣 AI 芯片已為商米、多度、小米、SK 電訊、永輝等上百家 AIoT 合作夥伴提供全面的賦能服務,覆蓋智慧城市、智慧商業、智慧社區、智慧教育、智能家居等多種應用場景。在合作過程中,推出了不少技術產品和方案。

  例如,基於地平線針對零售場景的 AIoT 芯片解決方案,商米推出了 AI 識客攝像機,該款攝像機可在本地端主動識別進店顧客並進行豐富的屬性鑒別,解決商家“看店難”問題,助力零售商的精準營銷。


商米 AI 識客攝像機

  同時,地平線還為小米一系列智能設備(主要為四款語音交互產品),提供自主研發的增強語音抽取(Enhanced Speech Extraction,簡稱 ESE)方案,助力小米音響“聽的准”,使其在複雜的噪聲環境中亦可實現隨時打斷、隨時喚醒,為用戶帶來更為精準、流暢的交互體驗。  

  另外,SK 電訊作為解決方案提供商,也整合地平線的邊緣 AI 智能攝像機方案與其自有雲平台,為零售行業提供端雲完整解決方案,加速產業升級。

  地平線將通過 Wintel 模式,成為平台型賦能者

  在技術護航下,地平線認為,其 AIoT 優勢將會助力地平線成為更具效能優勢、更強方案能力、更加開放靈活的邊緣 AI 芯片企業,在未來市場中佔據一定份額。

  • 極致的效能優勢:地平線率先提出將世界領先的深度學習算法集成在自主研發的邊緣 AI 處理器及平台上,通過為客戶提供軟硬一體化方案,發揮極致效能優勢; 
  • 完整的方案能力:地平線擁有豐富的算法樣例模型,能夠為客戶提供以芯片+算法+工具鏈為核心的完整方案,滿足多樣化場景需求; 
  • 與同類型產品相比,更加靈活高效的落地能力:  
  •  軟硬協同,高 MAC 利用率,實際任務處理表現更好; 
  • 支持新模型優化,如 MobileNet、Faster RCNN。在運行 MobileNet V2、Yolo V3 等業界領先的高效模型方面,旭日二代能夠達到甚至超過業內標稱 4TOPS 算力的 AI 芯片,而其功耗僅為 2W;
  • – 相較於市場主流旗艦 AI 芯片,地平線支持更主流的訓練框架,包括 Tensorflow、PyTorch、MXNet、ONNX、Caffe。
  • 成本優勢:可在以下方面幫助客戶降低成本:設備投入——無需購置大型服務器或佔用較大寬帶網絡; 設備運維—日常維護,更新迭代方便; 

  在此基礎之上,地平線表示未來將通過 Wintel 模式,成為平台型賦能者。“地平線的終局目標是成為邊緣人工智能平台的領導者,通過 Wintel 模式,即操作系統與處理器架構整合,成為平台型賦能者,為邊緣人工智能提供底層賦能。”

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

【精選推薦文章】

平板收購,iphone手機收購,二手筆電回收,二手iphone收購-全台皆可收購

收購3c,收購IPHONE,收購蘋果電腦-詳細收購流程一覽表

高價3c回收,收購空拍機,收購鏡頭,收購 MACBOOK-更多收購平台討論專區

3c收購,鏡頭 收購有可能以全新價回收嗎?

賣IPHONE,iPhone回收,舊換新!教你怎麼賣才划算?

蘋果官方解讀AirPods Pro:全新設計具備主動降噪功能

  作者:懶貓

  IT 之家 10 月 29 日消息 今日凌晨,蘋果正式發布了全新的 AirPods Pro 入耳式耳機。現在官方的詳細解讀也來了,一起來看一下吧。

  Apple 今日宣布推出 AirPods Pro,採用精巧輕盈的全新入耳式設計,擁有主動降噪功能與令人沉浸的卓越音質,為 AirPods 產品系列添加了一款不同凡響之作。AirPods Pro 將於今日起接受訂購,並於 10 月 30 日星期三開始在零售店內發售。

  “AirPods 是我們很暢銷的一款耳機。簡單的一鍵設置、令人讚歎的音質和標誌性的設計,讓它成為一款備受人們喜愛的 Apple 產品。而這次推出的 AirPods Pro,更將這種妙不可言的體驗提升到新的境界。”Apple 全球市場營銷高級副總裁 Phil Schiller 表示,“全新的入耳式 AirPods Pro 通過自適應均衡功能帶來動人音質,提供富有彈性的耳塞來確保佩戴舒適度和貼合度,還擁有創新的主動降噪功能和通透模式。我們相信,廣大用戶將會愛上 AirPods 家族的這位新成員。”

  AirPods Pro 與現有的 AirPods 系列一樣,有着與眾不同的無線音頻體驗。每款耳機都採用先進技術,重新定義了人們聽音樂、打電話、觀看影視劇、玩遊戲以及與 Siri 互動的方式。現有 AirPods 中深受用戶喜愛的巧妙設置體驗,在 AirPods Pro 上也得以延續。只需靠近 iOS 或 iPadOS 設備並打開充電盒,再輕點一下,便可將 AirPods Pro 與用戶已登錄的 iCloud 賬戶下各種設備配對,包括 iPhone、iPad、Mac、Apple Watch、Apple TV 和 iPod touch。

  全新設計

  AirPods 以突破性的設計顛覆了人們的無線音頻體驗,而新款 AirPods Pro 則從舒適度和貼合度出發,以精巧輕盈的全新入耳式耳機設計將這種體驗帶到一個新的高度。每隻耳機都隨附三種不同尺寸的耳塞,柔軟且富有彈性的硅膠材質令耳塞能貼合每隻耳朵的耳形,在保證佩戴舒適的同時可實現高密合度,而這也是實現聲聲入耳更沉浸的關鍵所在。為了進一步提升舒適度,AirPods Pro 還運用創新的通氣系統來平衡壓力,盡可能減輕其他入耳式耳機設計中常見的不適感。更棒的是,AirPods Pro 還可抗汗抗水,非常適合充滿活力的生活方式。

  主動降噪

  AirPods Pro 的主動降噪功能利用兩個麥克風結合先進的軟件,根據個人的耳形及耳機的佩戴貼合度持續進行調節。這種設計可消除背景噪音,帶來與眾不同、量身定製的出色降噪體驗,讓用戶無論是聽歌還是打電話,都能聽得更專註。

  第一個麥克風採用外向式設計,會檢測外部聲波並分析環境噪音。隨後,在這些背景噪音進入用戶耳朵之前,AirPods Pro 會用與之相當的抗噪聲波將其抵消。第二個麥克風為內向式設計,會檢測耳內噪音,然後 AirPods Pro 會將檢測到的多餘噪音也予以抵消。降噪功能會以每秒 200 次的頻率持續調節聲音信號。

  令人沉浸的音質

  AirPods Pro 可通過自適應均衡功能實現動人音質,該功能會根據用戶個人的耳形來自動調節音樂的低頻和中頻部分,營造出細膩豐富、令人沉浸的聆聽體驗。定製的高動態範圍放大器可呈現純凈清澈的聲音,並延長電池續航,另外還能驅動定製的高振幅、低失真度揚聲器驅動單元,優化音質並消除背景噪音。驅動器單元可實現渾厚一致、低至 20Hz 的低音,並在中頻和高頻的表現上帶來豐富細節。

  通透模式

  通透模式讓用戶可以選擇在聆聽音樂的同時,還能聽到周圍環境的聲音,比如外出跑步時聽到路上的熙熙攘攘,或是早晨上班途中及時聽到地鐵的重要通知。通透模式配合平衡壓力的通氣系統和先進的軟件,可以只保留適度的降噪調節,確保用戶自己的語音聽起來自然,同時音頻仍能以很棒的效果持續播放。

  主動降噪與通透模式切換起來很簡單,可直接通過 AirPods Pro 耳機柄上創新的力度感應器來完成。通過該力度感應器,還能輕鬆執行播放、暫停、跳過曲目、接聽或掛斷電話等操作。用戶也可在 iPhone 和 iPad 上按下控制中心的音量滑塊,或是播放音樂時在 Apple Watch 上點按“隔空播放”圖標,便捷地控制各種設置。

  性能與電池續航

  AirPods Pro 外形小巧卻性能強大,這要歸功於它開創性的 System-in-Package (SiP)封裝設計,而 Apple 設計的 H1 芯片正是其核心所在。H1 芯片擁有 10 個音頻核心,能為音頻和 Siri 等方方面面提供強勁動力。而它極低的音頻處理延遲,不僅能實現實時降噪,運用自適應技術呈現卓越音質,還可響應你的“嘿 Siri”免提語音請求,所有這些都能同時完成。

  AirPods Pro 擁有與 AirPods 一樣出色的電池續航力,聆聽時間最長可達 5 小時。在主動降噪模式下,AirPods Pro 單次充電聆聽時間最長可達 4.5 小時,通話時間最長可達 3.5 小時。配合無線充電盒進行額外多次充電,AirPods Pro 甚至可提供超過 24 小時的聆聽時間或超過 18 小時的通話時間。充電非常簡單,只需將 AirPods 充電盒放在 Qi 認證充電板上,或在外出時使用閃電接口,即可輕鬆為你的 AirPods Pro 充電。

  其他功能

  耳塞貼合度測試通過測試耳塞的密合度,確定最適合用戶的耳塞尺寸,幫助確保用戶佩戴 AirPods Pro 時獲得最佳的音頻體驗。用戶將 AirPods Pro 戴入耳中之後,先進的算法會與每隻 AirPods 內的麥克風配合,測量耳內的聲級,並將其與揚聲器驅動單元傳來的聲音進行比較。只需短短几秒,算法便可測得耳塞是尺寸合適且貼合度良好,還是需要進一步調整來達到更好的密合度。

  語音激活 Siri 功能利用 H1 芯片實現免提“嘿 Siri”功能,輕鬆播放歌曲、調高音量、撥打電話或獲取路線信息。

  音頻共享功能讓你只需再拿一副 AirPods 靠近 iPhone 或 iPad,便可與好友一起欣賞同一首歌曲或同一部電影。

  信息播報功能可在收到信息時,自動、便捷地為你讀出信息,而在你接打電話或通過音頻共享功能分享媒體內容時,Siri 也懂得不要打擾你。

  價格與上市時間

  AirPods Pro 售價為 1999 元,即日起將通過蘋果官網和 Apple Store App 在超過 25 個國家和地區接受訂購。AirPods Pro 將於本周稍晚些時候開始在零售店有售(具體時間因國家和地區而異)。

  使用 AirPods Pro 需使用運行 iOS 13.2 或更新版本、iPadOS 13.2 或更新版本、watchOS 6.1 或更新版本、tvOS 13.2 或更新版本、macOS Catalina 10.15.1 或更新版本的 Apple 設備。

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

【精選推薦文章】

高價收購3C產品,價格不怕你比較

收購3c瘋!各款手機、筆電、相機、平板,歡迎來詢價!

公開收購3c價格,不怕被賤賣!

買房后樓盤沒動工 房款退了 “電商費”遲遲不退

買房後樓盤沒動工房款退了“電商費”遲遲不退

2019年03月21日17:06:11來源:大河網編輯:小徐
摘要:買房後樓盤一直沒動工房款退了“電商費”遲遲不退購房者交納“電商費”的收據單河南商報記者楊琨/攝製圖/楊芳芳河南商報記者張晶晶(function() { var s = “_……

買房後樓盤一直沒動工房款退了“電商費”遲遲不退

購房者交納“電商費”的收據單河南商報記者楊琨/攝

製圖/楊芳芳

河南商報記者張晶晶

去年11月20日,河南商報“記者陪你去辦事兒”欄目曾關注過鄭州一樓盤業主被違規收取“電商費”一事。

無獨有偶,近日,鄭州的李女士向河南商報記者反映,她在鑫港花苑買房時也交了“電商費”,然而由於種種原因,該地產項目一直沒動工,開發商將房款退還給了她,但她所交的1萬元“電商費”卻遲遲不退。

事件

樓盤一直沒開工

退了首付款卻沒退“電商費”

3月4日,在鄭州市嵩山南路與長江中路交叉口,聚集了幾十名和李女士有類似經歷的購房者。

“2017年我通過晟耀(電商公司)的置業顧問在新密曲梁買了房子,樓盤叫鑫港花苑,交了首付款後又交了1萬元’電商費’,說是交完’電商費’,買房可以優惠8個點。”李女士表示,該樓盤項目在新密曲梁,而電商公司的註冊地是鄭州。 

她說,購房合同上標明2019年6月底交房,但由於該地產開發項目五證不全,遲遲沒有動工。去年年底,開發商鄭州歐普倫置業有限公司與她簽訂了退款協議,將首付款退還給了她。 

“我們這些購房者,’電商費’有交1萬的,有交2萬的,還有交了3萬的。”李女士稱,首付款退了,“電商費”遲遲不見退。幾個月過去了,退過首付款的購房者們坐不住了。

河南商報記者就此詢問開發商時,該項目開發商鄭州歐普倫置業有限公司一山姓負責人表示,“電商費”實則是中介費,是銷售費用,消費者想退錢,需要找電商公司。 

3月4日上午,河南商報記者與一眾購房者一起來到上述電商公司——鄭州晟耀房地產營銷策劃有限責任公司(以下簡稱“晟耀地產”),詢問業主們的錢款去向和退款事宜。

回應

電商公司:錢可以退,但要等三方一起協商

在晟耀地產的辦公室,該公司法定代表人牛磊表示,“電商費”可以退,但是只退給晟耀地產的客戶,帶上退款協議和收據,三天之內工作人員會聯繫退款。

河南商報記者發現,在不少購房者的收據單上,收款人一欄,有的僅有“晟耀”二字,有的標註的是“晟耀—眾凱”“晟耀—垚陽”等字樣。這是啥意思?

“我們幾個(公司老闆)關係比較好,是聯合賣房的,只是我和開發商關係稍微好了些,先接觸的,所以前面都掛了個晟耀。我們一起賣房的時候都很高興,出了事之後這些人都不想管,推給我,但是錢都是他們收的。”牛磊稱,在場大部分客戶的“電商費”,都是其他幾個分銷公司收的,客戶理應去找這些公司要錢。

河南商報記者電話採訪了分銷公司眾凱房產一位李姓負責人,對方表示,自己目前在新鄉,對於客戶的退費訴求,他答應退費,但作為分銷公司,需要與晟耀地產、開發商三方一起協商如何退款。 

而河南商報記者從晟耀地產的客戶李先生處了解到,晟耀地產方面稱,由於業務員拿走了一半的費用,因此“電商費”只同意退一半。 

維權

“電商費”被明令禁止,投訴卻被多部門“踢皮球”

遇此情況,該如何維護個人權益?

河南商報記者撥打了12315消費者投訴電話,對方表示,關於房產的消費投訴,建議將情況反映至房管局。

據悉,去年7月9日,河南省住房和城鄉建設廳發布《關於進一步規範商品房銷售行為的通知》(豫建房管〔2018〕29號文),規定房地產開發企業不得採取“電商”等合作模式,通過第三方在網簽合同約定價款之外加價出售房屋或者價外收取“團購費”“會員費”“信息諮詢費”等費用。若有以上行為的,根據情節輕重,由當地房地產主管部門採取約談企業負責人並責令限期改正、整改到位前暫停網簽、典型案例曝光等方式進行處置。

3月4日下午,河南商報記者跟隨購房者們來到了二七區住房保障服務中心。一工作人員表示,投訴舉報要向上級部門鄭州市房管局反映。

隨後,在鄭州市房管局信訪辦,一工作人員表示,由於該房產開發項目在新密,而電商公司也是由新密的開發商引入的,因此,應向新密市房管局反映。“不歸我們管。”該工作人員稱。

隨後,河南商報記者以消費投訴之名,向新密市房管局反映了該情況,對方卻表示管不了。“中介公司是屬於鄭州的,我們管不了。項目雖然是新密的,但是開發商已經把錢退了,現在只是中介不退錢,中介屬於鄭州,你應該向鄭州市房管局反映。” 

記者將新密市房管局的回應反映給了鄭州市房管局信訪辦,該工作人員表示,新密市房管局推了,就是他們不作為。

進展

電商公司組織購房者們協商

承諾月底給結果

3月19日,購房者任濤(化名)告訴河南商報記者,電商公司晟耀地產法定代表人牛磊組織一眾購房者,與開發商、分銷公司協商“電商費”退費事宜。

“說是開發商、分銷公司都來,但是最後就只有牛磊去了,也沒有成功退費,就只是讓大家登記了一下。”3月20日,購房者李女士告訴河南商報記者,晟耀地產負責人承諾,會去開發商處商議,3月30日,再具體告訴大家商議的結果。

“新密市房管局我們這些業主去了很多次了,但是他們不管這事,他們說開發商已經把房款包括利息都退了,已經不歸新密管了。”李女士稱。

若是到了月底,“電商費”依然無法退還,李女士表示,她們將走法律程序維護權益,“再不退費只有打官司了。”

免責聲明:本文僅代表作者個人觀點,與房途網無關。其原創性以及文中陳述文字和內容未經本站證實,對本文以及其中全部或者部分內容、文字的真實性、完整性、及時性本站不作任何保證或承諾,請讀者僅作參考,並請自行核實相關內容。本站聲明:網站內容來自房途網http://www.fangtoo.com/

【室內裝潢設計廠商推薦】

新屋購入,尋找台中室內設計師?是否可先免費估價丈量?

想要打造簡約、淡雅兼且收納空間的小資房,台中室內設計推薦哪一家?

中古屋大改造,分享台中室內設計公司隔間重新規劃,擴大實用空間!

搜狐焦點舉辦20周年盛典暨品牌戰略發布會

搜狐焦點舉辦20週年盛典暨品牌戰略發布會

2019年03月21日17:21:46來源:中國新聞網編輯:小徐
摘要:中新網3月21日電20日,搜狐焦點20週年盛典暨品牌戰略發布會於北京舉辦。搜狐焦點20週年盛典現場,匯力基金董事長、中房集團理事長孟曉蘇,林達集團董事局主席李曉林,當代置業執行董事兼總裁、中經聯盟輪……

中新網3月21日電20日,搜狐焦點20週年盛典暨品牌戰略發布會於北京舉辦。搜狐焦點20週年盛典現場,匯力基金董事長、中房集團理事長孟曉蘇,林達集團董事局主席李曉林,當代置業執行董事兼總裁、中經聯盟輪值主席張鵬,京投發展副董事長、總裁高一軒,中經聯盟秘書長、優鋪網董事長陳雲峰等業內大咖紛紛出席,共同探討中國房企如何尋找出一條跑贏大勢的戰略佈局。

搜狐焦點總經理鮑全勝表示,2019,搜狐焦點站在又一個20年的起點,未來將繼續發揮媒體屬性,以用戶為導向,堅持內容為王,專業至上,產品覆蓋房地產營銷的全環節,希望給用戶帶來更專業的指導,讓買房更簡單更省心。

向未來,共生長。歷史的車輪由工業文明載入互聯網文明,作為一家以科技創新為主導的互聯網企業,搜狐焦點因勢而變,順勢而為,在產品革新、技術創新和服務更新方面做出了積極的嘗試和應對。盛典現場,搜狐焦點產品技術中心高級總監李少鵬就產品革新、技術創新和服務更新等方面作了詳細解讀。

作為搜狐旗下專業的房產網站,搜狐焦點在2019年,更是全面打通內部資源,與搜狐新聞客戶端、搜狐財經、搜狐視頻等頻道達成深度合作,尤其與搜狐新聞客戶端牢牢綁定,依託搜狐新聞客戶端龐大的裝機量,全面佔領房產領域移動端陣地。盛典現場,搜狐新聞客戶端及媒體商業產品技術副總裁張智敏上台致辭,為搜狐焦點的二十歲生日送上祝福。搜狐集團運營人力資源副總裁張雪梅、搜狐焦點總經理鮑全勝、產品技術中心高級總監李少鵬上台,共同啟動《百城戰略》。

公益已經成為搜狐焦點不可剝離的固有組成部分。此屆盛典特別涵蓋公益捐贈企業授牌、公益受助兒童獻禮兩個模塊,希望藉助企業自身的強大影響力,將社會愛心力量凝聚在一起,幫助孩子們獲得健康,享受幸福生活。用行動實踐慈善精神,讓社會與個人真正受益於善行。以愛傳遞愛,以心溫暖心,搜狐焦點公益基金,一路前行,未來可期。

搜狐焦點在20年的發展歷程中,為了更好的服務客戶,服務終端消費者,已經將創新求變注入企業的發展基因,以持續的內容生產力、強大的整合營銷力、以及先進的技術創新力,搭建起了富有特色的價值矩陣,這一路也得到了廣大客戶和社會各界的提攜、信賴和幫助。

藉此20週年之際,搜狐焦點也評選出了一批具有行業影響力的人物、具有市場公信力的企業、具有市場競爭力的項目,以此來致敬他們在北京樓市前行中的典範力量。

免責聲明:本文僅代表作者個人觀點,與房途網無關。其原創性以及文中陳述文字和內容未經本站證實,對本文以及其中全部或者部分內容、文字的真實性、完整性、及時性本站不作任何保證或承諾,請讀者僅作參考,並請自行核實相關內容。本站聲明:網站內容來自房途網http://www.fangtoo.com/

【磁磚設計與裝修美學相關資訊】

挑好磚一點都不難!馬賽克磚挑選眉角小撇步!

浴室設計小心機,利用馬賽克磁磚,放大你的浴室空間

想改變客廳裝潢風格嗎? 馬賽克拼貼打造溫馨鄉村風,教你如何運用馬賽克瓷磚自行DIY創作