單例模式,反射破環?_包裝設計

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

窩窩觸角包含自媒體、自有平台及其他國家營銷業務等,多角化經營並具有國際觀的永續理念。

餓漢式

// 餓漢式單例
public class Hungry {
    
    //構造器私有
    private Hungry(){

    }
    // 一上來就把這個類加載了
    private final static  Hungry HUNGRY = new Hungry();

    public static Hungry getInstance(){
        return HUNGRY;
    }
}
// 餓漢式單例
public class Hungry {
    // 這4組數據非常耗內存資源,餓漢式一上來就把所有的內存裏面的東西全部加載進來了,就存在這個空間
    // 但這個空間現在是沒有使用的,可能會造成浪費空間
    private byte[] data1 = new byte[1024*1024];
    private byte[] data2 = new byte[1024*1024];
    private byte[] data3 = new byte[1024*1024];
    private byte[] data4 = new byte[1024*1024];
    
    //構造器私有
    private Hungry(){

    }
    // 一上來就把這個類加載了
    private final static  Hungry HUNGRY = new Hungry();

    public static Hungry getInstance(){
        return HUNGRY;
    }

}

餓漢式單例可能會造成浪費空間,所以想要用的時候再去創建這個對象,平時就先放在這個地方,於是就出現了懶漢式!

懶漢式

// 懶漢式單例
public class LazyMan {
	
   // 構造器私有
    private LazyMan(){
       
    }

    private  static LazyMan lazyMan;

   
    public static LazyMan getInstance(){
       
        if (lazyMan==null){
            lazyMan = new LazyMan(); 
        }
        return lazyMan;
    }
}

它是有問題的,單線程下確實單例ok,多線程併發就會出現問題!

測試

// 懶漢式單例
public class LazyMan {
	
   // 構造器私有
    private LazyMan(){
       System.out.println(Thread.currentThread().getName()+":: ok");
    }

    private  static LazyMan lazyMan;

   
    public static LazyMan getInstance(){
       
        if (lazyMan==null){
            lazyMan = new LazyMan(); 
        }
        return lazyMan;
    }
    
    public static void main(String[] args) {

        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                LazyMan.getInstance();
            }).start();
        }
    }
}

發現單例有問題,每次結果可能都不一樣!

解決

// 懶漢式單例
public class LazyMan {


    private LazyMan(){
        System.out.println(Thread.currentThread().getName()+":: ok");
    }

    private  static LazyMan lazyMan;

    // 雙重檢測鎖模式的 懶漢式單例 DCL懶漢式
    public static LazyMan getInstance(){
        if (lazyMan==null){
            synchronized (LazyMan.class){
                if (lazyMan==null){
                    lazyMan = new LazyMan(); 
                }
            }
        }
        return lazyMan;
    }

     public static void main(String[] args) {

         for (int i = 0; i < 10 ; i++) {
             new Thread(()->{
                LazyMan.getInstance();
             }).start();
         }
     }
}

但在極端情況下還是可能出現問題

經歷三個步驟:

1、 分配內存空間

2、 執行構造方法,初始化對象

3、 把這個對象指向這個空間

有可能會發生指令重排的操作!

比如,期望它執行 123 ,但是它真實可能執行132,比如第一個A線程過來執行了132,先分配空間再吧這個空間佔用了,佔用之後再去執行構造方法,如果現在突然來了個B線程,由於A已經指向這個空間了,它會以為這個 lazyMan 不等於 null ,直接return ,此時lazyMan還沒有完成構造,所以必須避免這個問題!

必須加上volatile

// 懶漢式單例
public class LazyMan {


    private LazyMan(){
        System.out.println(Thread.currentThread().getName()+":: ok");
    }
	// 避免指令重排
    private volatile static LazyMan lazyMan;

    // 雙重檢測鎖模式的 懶漢式單例 DCL懶漢式
    public static LazyMan getInstance(){
        if (lazyMan==null){
            synchronized (LazyMan.class){
                if (lazyMan==null){
                    lazyMan = new LazyMan(); 
                }
            }
        }
        return lazyMan;
    }

     public static void main(String[] args) {

         for (int i = 0; i < 10 ; i++) {
             new Thread(()->{
                LazyMan.getInstance();
             }).start();
         }
     }
}

靜態內部類

// 靜態內部類
public class Holder {
    private Holder(){

    }
    public static Holder getInstance(){
        return InnerClass.HOLDER;
    }
    public static  class InnerClass{
        private static final Holder HOLDER = new Holder();
    }
}

也是單例模式的一種,不安全!

單例不安全 反射

// 懶漢式單例
public class LazyMan {


    private LazyMan(){
        System.out.println(Thread.currentThread().getName()+":: ok");
    }

    private volatile static LazyMan lazyMan;

    // 雙重檢測鎖模式的 懶漢式單例 DCL懶漢式
    public static LazyMan getInstance(){
        if (lazyMan==null){
            synchronized (LazyMan.class){
                if (lazyMan==null){
                    lazyMan = new LazyMan(); //不是一個原子性操作
                }
            }
        }
        return lazyMan;
    }


    //反射
     public static void main(String[] args) throws Exception {
         LazyMan instance1 = LazyMan.getInstance();
         Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
         declaredConstructor.setAccessible(true); // 無視了私有的構造器
         // 通過反射創建對象
         LazyMan instance2 = declaredConstructor.newInstance();

         System.out.println(instance1);
         System.out.println(instance2);
     }
}

結論:反射可以破壞這種單例

解決

// 懶漢式單例
public class LazyMan {

    private LazyMan(){
        synchronized (LazyMan.class){
            if (lazyMan!=null){
                throw new RuntimeException("不要試圖使用反射破環 異常");
            }
        }
        System.out.println(Thread.currentThread().getName()+":: ok");
    }

    private volatile static LazyMan lazyMan;

    // 雙重檢測鎖模式的 懶漢式單例 DCL懶漢式
    public static LazyMan getInstance(){
        if (lazyMan==null){
            synchronized (LazyMan.class){
                if (lazyMan==null){
                    lazyMan = new LazyMan(); //不是一個原子性操作
                }
            }
        }
        return lazyMan;
    }

    //反射
     public static void main(String[] args) throws Exception {
         LazyMan instance1 = LazyMan.getInstance();
         Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
         declaredConstructor.setAccessible(true); // 無視了私有的構造器
         // 通過反射創建對象
         LazyMan instance2 = declaredConstructor.newInstance();

         System.out.println(instance1);
         System.out.println(instance2);
     }
}

但是如果都用反射創建對象的情況下,還是會破環單例!

測試

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

上新台中搬家公司提供您一套專業有效率且人性化的辦公室搬遷、公司行號搬家及工廠遷廠的搬家服務

解決

// 懶漢式單例
public class LazyMan {
      // 標誌位
    private static boolean abc = false;

    private LazyMan(){
        synchronized (LazyMan.class){
            if (abc==false){
                abc=true;
            }else {
                throw new RuntimeException("不要試圖使用反射破環 異常");
            }
          
        }
        System.out.println(Thread.currentThread().getName()+":: ok");
    }
    private volatile static LazyMan lazyMan;

    // 雙重檢測鎖模式的 懶漢式單例 DCL懶漢式
    public static LazyMan getInstance(){
        if (lazyMan==null){
            synchronized (LazyMan.class){
                if (lazyMan==null){
                    lazyMan = new LazyMan(); //不是一個原子性操作
                }
            }
        }
        return lazyMan;
    }

    //反射
    public static void main(String[] args) throws Exception {
        //LazyMan instance1 = LazyMan.getInstance();
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true); // 無視了私有的構造器
        // 通過反射創建對象
        LazyMan instance2 = declaredConstructor.newInstance();
        LazyMan instance1 = declaredConstructor.newInstance();

        System.out.println(instance1);
        System.out.println(instance2);
    }
}

但是如果被人知道 abc這個變量,也可以破環!

單例又被破環了!

看一下源碼

它說不能使用反射破環枚舉,枚舉是jdk1.5出現的,自帶單例模式!

測試,寫一個枚舉類

// enum 本身就是一個class類
public enum EnumSingle {

    INSTANCE;
    public EnumSingle getInstance(){
        return INSTANCE;
    }
}

查看它的源碼

試圖破環!

// enum 本身就是一個class類
public enum EnumSingle {

    INSTANCE;
    public EnumSingle getInstance(){
        return INSTANCE;
    }
}
class Test{
    public static void main(String[] args) throws Exception {
        EnumSingle instance1 = EnumSingle.INSTANCE;
        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        EnumSingle instance2 = declaredConstructor.newInstance();
        System.out.println(instance1);
        System.out.println(instance2);
    }
}

它竟然說我現在的這個枚舉類中沒有空參構造器!

然後就去源碼里分析!

找到這個class文件!利用javap反編譯一下!

發現這個也显示有一個空參構造,證明這個也不對,用第三方的工具查看!

利用它再吧class文件生成java文件!

打開這個java文件

證明是idea和源碼騙了我!

再次嘗試破環!

// enum 本身就是一個class類
public enum EnumSingle {

    INSTANCE;
    public EnumSingle getInstance(){
        return INSTANCE;
    }
}
class Test{
    public static void main(String[] args) throws Exception {
        EnumSingle instance1 = EnumSingle.INSTANCE;
        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
        declaredConstructor.setAccessible(true);
        EnumSingle instance2 = declaredConstructor.newInstance();
        System.out.println(instance1);
        System.out.println(instance2);
    }
}

結論:反射無法破環枚舉類!

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

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

網動廣告出品的網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上她。

基於 abp vNext 和 .NET Core 開發博客項目 – 定時任務最佳實戰(二)_貨運

※智慧手機時代的來臨,RWD網頁設計為架站首選

網動結合了許多網際網路業界的菁英共同研發簡單易操作的架站工具,及時性的更新,為客戶創造出更多的網路商機。

系列文章

  1. 基於 abp vNext 和 .NET Core 開發博客項目 – 使用 abp cli 搭建項目
  2. 基於 abp vNext 和 .NET Core 開發博客項目 – 給項目瘦身,讓它跑起來
  3. 基於 abp vNext 和 .NET Core 開發博客項目 – 完善與美化,Swagger登場
  4. 基於 abp vNext 和 .NET Core 開發博客項目 – 數據訪問和代碼優先
  5. 基於 abp vNext 和 .NET Core 開發博客項目 – 自定義倉儲之增刪改查
  6. 基於 abp vNext 和 .NET Core 開發博客項目 – 統一規範API,包裝返回模型
  7. 基於 abp vNext 和 .NET Core 開發博客項目 – 再說Swagger,分組、描述、小綠鎖
  8. 基於 abp vNext 和 .NET Core 開發博客項目 – 接入GitHub,用JWT保護你的API
  9. 基於 abp vNext 和 .NET Core 開發博客項目 – 異常處理和日誌記錄
  10. 基於 abp vNext 和 .NET Core 開發博客項目 – 使用Redis緩存數據
  11. 基於 abp vNext 和 .NET Core 開發博客項目 – 集成Hangfire實現定時任務處理
  12. 基於 abp vNext 和 .NET Core 開發博客項目 – 用AutoMapper搞定對象映射
  13. 基於 abp vNext 和 .NET Core 開發博客項目 – 定時任務最佳實戰(一)
  14. 基於 abp vNext 和 .NET Core 開發博客項目 – 定時任務最佳實戰(二)
  15. 基於 abp vNext 和 .NET Core 開發博客項目 – 定時任務最佳實戰(三)
  16. 基於 abp vNext 和 .NET Core 開發博客項目 – 博客接口實戰篇(一)
  17. 基於 abp vNext 和 .NET Core 開發博客項目 – 博客接口實戰篇(二)
  18. 基於 abp vNext 和 .NET Core 開發博客項目 – 博客接口實戰篇(三)
  19. 基於 abp vNext 和 .NET Core 開發博客項目 – 博客接口實戰篇(四)
  20. 基於 abp vNext 和 .NET Core 開發博客項目 – 博客接口實戰篇(五)
  21. 基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(一)
  22. 基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(二)
  23. 基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(三)
  24. 基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(四)
  25. 基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(五)
  26. 基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(六)
  27. 基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(七)
  28. 基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(八)
  29. 基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(九)
  30. 基於 abp vNext 和 .NET Core 開發博客項目 – 終結篇之發布項目

上一篇(https://www.cnblogs.com/meowv/p/12971041.html)使用HtmlAgilityPack抓取壁紙數據成功將圖片存入數據庫,本篇繼續來完成一個全網各大平台的熱點新聞數據的抓取。

同樣的,可以先預覽一下我個人博客中的成品:https://meowv.com/hot ,和抓取壁紙的套路一樣,大同小異。

本次要抓取的源有18個,分別是博客園、V2EX、SegmentFault、掘金、微信熱門、豆瓣精選、IT之家、36氪、百度貼吧、百度熱搜、微博熱搜、知乎熱榜、知乎日報、網易新聞、GitHub、抖音熱點、抖音視頻、抖音正能量。

還是將數據存入數據庫,按部就班先將實體類和自定義倉儲創建好,實體取名HotNews。貼一下代碼:

//HotNews.cs
using System;
using Volo.Abp.Domain.Entities;

namespace Meowv.Blog.Domain.HotNews
{
    public class HotNews : Entity<Guid>
    {
        /// <summary>
        /// 標題
        /// </summary>
        public string Title { get; set; }

        /// <summary>
        /// 鏈接
        /// </summary>
        public string Url { get; set; }

        /// <summary>
        /// SourceId
        /// </summary>
        public int SourceId { get; set; }

        /// <summary>
        /// 創建時間
        /// </summary>
        public DateTime CreateTime { get; set; }
    }
}

剩下的大家自己完成,最終數據庫生成一張空的數據表,meowv_hotnews 。

然後還是將我們各大平台放到一個枚舉類HotNewsEnum.cs中。

//HotNewsEnum.cs
using System.ComponentModel;

namespace Meowv.Blog.Domain.Shared.Enum
{
    public enum HotNewsEnum
    {
        [Description("博客園")]
        cnblogs = 1,

        [Description("V2EX")]
        v2ex = 2,

        [Description("SegmentFault")]
        segmentfault = 3,

        [Description("掘金")]
        juejin = 4,

        [Description("微信熱門")]
        weixin = 5,

        [Description("豆瓣精選")]
        douban = 6,

        [Description("IT之家")]
        ithome = 7,

        [Description("36氪")]
        kr36 = 8,

        [Description("百度貼吧")]
        tieba = 9,

        [Description("百度熱搜")]
        baidu = 10,

        [Description("微博熱搜")]
        weibo = 11,

        [Description("知乎熱榜")]
        zhihu = 12,

        [Description("知乎日報")]
        zhihudaily = 13,

        [Description("網易新聞")]
        news163 = 14,

        [Description("GitHub")]
        github = 15,

        [Description("抖音熱點")]
        douyin_hot = 16,

        [Description("抖音視頻")]
        douyin_video = 17,

        [Description("抖音正能量")]
        douyin_positive = 18
    }
}

和上一篇抓取壁紙一樣,做一些準備工作。

.Application.Contracts層添加HotNewsJobItem<T>,在.BackgroundJobs層添加HotNewsJob用來處理爬蟲邏輯,用構造函數方式注入倉儲IHotNewsRepository

//HotNewsJobItem.cs
using Meowv.Blog.Domain.Shared.Enum;

namespace Meowv.Blog.Application.Contracts.HotNews
{
    public class HotNewsJobItem<T>
    {
        /// <summary>
        /// <see cref="Result"/>
        /// </summary>
        public T Result { get; set; }

        /// <summary>
        /// 來源
        /// </summary>
        public HotNewsEnum Source { get; set; }
    }
}
//HotNewsJob.CS
using Meowv.Blog.Domain.HotNews.Repositories;
using System;
using System.Net.Http;
using System.Threading.Tasks;

namespace Meowv.Blog.BackgroundJobs.Jobs.HotNews
{
    public class HotNewsJob : IBackgroundJob
    {
        private readonly IHttpClientFactory _httpClient;
        private readonly IHotNewsRepository _hotNewsRepository;

        public HotNewsJob(IHttpClientFactory httpClient,
                          IHotNewsRepository hotNewsRepository)
        {
            _httpClient = httpClient;
            _hotNewsRepository = hotNewsRepository;
        }

        public async Task ExecuteAsync()
        {
            throw new NotImplementedException();
        }
    }
}

接下來明確數據源地址,因為以上數據源有的返回是HTML,有的直接返回JSON數據。為了方便調用,我這裏還注入了IHttpClientFactory

整理好的待抓取數據源列表是這樣的。

...
var hotnewsUrls = new List<HotNewsJobItem<string>>
{
    new HotNewsJobItem<string> { Result = "https://www.cnblogs.com", Source = HotNewsEnum.cnblogs },
    new HotNewsJobItem<string> { Result = "https://www.v2ex.com/?tab=hot", Source = HotNewsEnum.v2ex },
    new HotNewsJobItem<string> { Result = "https://segmentfault.com/hottest", Source = HotNewsEnum.segmentfault },
    new HotNewsJobItem<string> { Result = "https://web-api.juejin.im/query", Source = HotNewsEnum.juejin },
    new HotNewsJobItem<string> { Result = "https://weixin.sogou.com", Source = HotNewsEnum.weixin },
    new HotNewsJobItem<string> { Result = "https://www.douban.com/group/explore", Source = HotNewsEnum.douban },
    new HotNewsJobItem<string> { Result = "https://www.ithome.com", Source = HotNewsEnum.ithome },
    new HotNewsJobItem<string> { Result = "https://36kr.com/newsflashes", Source = HotNewsEnum.kr36 },
    new HotNewsJobItem<string> { Result = "http://tieba.baidu.com/hottopic/browse/topicList", Source = HotNewsEnum.tieba },
    new HotNewsJobItem<string> { Result = "http://top.baidu.com/buzz?b=341", Source = HotNewsEnum.baidu },
    new HotNewsJobItem<string> { Result = "https://s.weibo.com/top/summary/summary", Source = HotNewsEnum.weibo },
    new HotNewsJobItem<string> { Result = "https://www.zhihu.com/api/v3/feed/topstory/hot-lists/total?limit=50&desktop=true", Source = HotNewsEnum.zhihu },
    new HotNewsJobItem<string> { Result = "https://daily.zhihu.com", Source = HotNewsEnum.zhihudaily },
    new HotNewsJobItem<string> { Result = "http://news.163.com/special/0001386F/rank_whole.html", Source = HotNewsEnum.news163 },
    new HotNewsJobItem<string> { Result = "https://github.com/trending", Source = HotNewsEnum.github },
    new HotNewsJobItem<string> { Result = "https://www.iesdouyin.com/web/api/v2/hotsearch/billboard/word", Source = HotNewsEnum.douyin_hot },
    new HotNewsJobItem<string> { Result = "https://www.iesdouyin.com/web/api/v2/hotsearch/billboard/aweme", Source = HotNewsEnum.douyin_video },
    new HotNewsJobItem<string> { Result = "https://www.iesdouyin.com/web/api/v2/hotsearch/billboard/aweme/?type=positive", Source = HotNewsEnum.douyin_positive },
};
...

其中有幾個比較特殊的,掘金、百度熱搜、網易新聞。

掘金需要發送Post請求,返回的是JSON數據,並且需要指定特有的請求頭和請求數據,所以使用IHttpClientFactory創建了HttpClient對象。

百度熱搜、網易新聞兩個老大哥玩套路,網頁編碼是GB2312的,所以要專門為其指定編碼方式,不然取到的數據都是亂碼。

※評比南投搬家公司費用收費行情懶人包大公開

搬家價格與搬家費用透明合理,不亂收費。本公司提供下列三種搬家計費方案,由資深專業組長到府估價,替客戶量身規劃選擇最經濟節省的計費方式

...
var web = new HtmlWeb();
var list_task = new List<Task<HotNewsJobItem<object>>>();

hotnewsUrls.ForEach(item =>
{
    var task = Task.Run(async () =>
    {
        var obj = new object();

        if (item.Source == HotNewsEnum.juejin)
        {
            using var client = _httpClient.CreateClient();
            client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.14 Safari/537.36 Edg/83.0.478.13");
            client.DefaultRequestHeaders.Add("X-Agent", "Juejin/Web");
            var data = "{\"extensions\":{\"query\":{ \"id\":\"21207e9ddb1de777adeaca7a2fb38030\"}},\"operationName\":\"\",\"query\":\"\",\"variables\":{ \"first\":20,\"after\":\"\",\"order\":\"THREE_DAYS_HOTTEST\"}}";
            var buffer = data.SerializeUtf8();
            var byteContent = new ByteArrayContent(buffer);
            byteContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");

            var httpResponse = await client.PostAsync(item.Result, byteContent);
            obj = await httpResponse.Content.ReadAsStringAsync();
        }
        else
        {
            Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
            obj = await web.LoadFromWebAsync(item.Result, (item.Source == HotNewsEnum.baidu || item.Source == HotNewsEnum.news163) ? Encoding.GetEncoding("GB2312") : Encoding.UTF8);
        }

        return new HotNewsJobItem<object>
        {
            Result = obj,
            Source = item.Source
        };
    });
    list_task.Add(task);
});
Task.WaitAll(list_task.ToArray());

循環 hotnewsUrls ,可以看到HotNewsJobItem我們返回的是object類型,因為有JSON又有HtmlDocument對象。所以這裏為了能夠統一接收,就是用了object。

針對掘金做了單獨處理,使用HttpClient發送Post請求,返回JSON字符串數據。

針對百度熱搜和網易新聞,使用Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);註冊編碼提供程序,然後在web.LoadFromWebAsync(...)加載網頁數據的時候指定網頁編碼,我使用了一個三元表達式來處理。

完成上面這一步,就可以循環 list_task,使用XPath語法,或者解析JSON數據,去拿到數據了。

...
var hotNews = new List<HotNews>();
foreach (var list in list_task)
{
    var item = await list;
    var sourceId = (int)item.Source;

    ...

    if (hotNews.Any())
    {
        await _hotNewsRepository.DeleteAsync(x => true);
        await _hotNewsRepository.BulkInsertAsync(hotNews);
    }
}

這個爬蟲同樣很簡單,只要拿到標題和鏈接即可,所以主要目標是尋找到頁面上的a標籤列表。這個我覺得也沒必要一個個去分析了,直接上代碼。

// 博客園
 if (item.Source == HotNewsEnum.cnblogs)
 {
     var nodes = ((HtmlDocument)item.Result).DocumentNode.SelectNodes("//div[@class='post_item_body']/h3/a").ToList();
     nodes.ForEach(x =>
     {
         hotNews.Add(new HotNews
         {
             Title = x.InnerText,
             Url = x.GetAttributeValue("href", ""),
             SourceId = sourceId,
             CreateTime = DateTime.Now
         });
     });
 }
// V2EX
if (item.Source == HotNewsEnum.v2ex)
{
    var nodes = ((HtmlDocument)item.Result).DocumentNode.SelectNodes("//span[@class='item_title']/a").ToList();
    nodes.ForEach(x =>
    {
        hotNews.Add(new HotNews
        {
            Title = x.InnerText,
            Url = $"https://www.v2ex.com{x.GetAttributeValue("href", "")}",
            SourceId = sourceId,
            CreateTime = DateTime.Now
        });
    });
}
 // SegmentFault
 if (item.Source == HotNewsEnum.segmentfault)
 {
     var nodes = ((HtmlDocument)item.Result).DocumentNode.SelectNodes("//div[@class='news__item-info clearfix']/a").Where(x => x.InnerText.IsNotNullOrEmpty()).ToList();
     nodes.ForEach(x =>
     {
         hotNews.Add(new HotNews
         {
             Title = x.SelectSingleNode(".//h4").InnerText,
             Url = $"https://segmentfault.com{x.GetAttributeValue("href", "")}",
             SourceId = sourceId,
             CreateTime = DateTime.Now
         });
     });
 }
// 掘金
if (item.Source == HotNewsEnum.juejin)
{
    var obj = JObject.Parse((string)item.Result);
    var nodes = obj["data"]["articleFeed"]["items"]["edges"];
    foreach (var node in nodes)
    {
        hotNews.Add(new HotNews
        {
            Title = node["node"]["title"].ToString(),
            Url = node["node"]["originalUrl"].ToString(),
            SourceId = sourceId,
            CreateTime = DateTime.Now
        });
    }
}
// 微信熱門
if (item.Source == HotNewsEnum.weixin)
{
    var nodes = ((HtmlDocument)item.Result).DocumentNode.SelectNodes("//ul[@class='news-list']/li/div[@class='txt-box']/h3/a").ToList();
    nodes.ForEach(x =>
    {
        hotNews.Add(new HotNews
        {
            Title = x.InnerText,
            Url = x.GetAttributeValue("href", ""),
            SourceId = sourceId,
            CreateTime = DateTime.Now
        });
    });
}
// 豆瓣精選
if (item.Source == HotNewsEnum.douban)
{
    var nodes = ((HtmlDocument)item.Result).DocumentNode.SelectNodes("//div[@class='channel-item']/div[@class='bd']/h3/a").ToList();
    nodes.ForEach(x =>
    {
        hotNews.Add(new HotNews
        {
            Title = x.InnerText,
            Url = x.GetAttributeValue("href", ""),
            SourceId = sourceId,
            CreateTime = DateTime.Now
        });
    });
}
// IT之家
if (item.Source == HotNewsEnum.ithome)
{
    var nodes = ((HtmlDocument)item.Result).DocumentNode.SelectNodes("//div[@class='lst lst-2 hot-list']/div[1]/ul/li/a").ToList();
    nodes.ForEach(x =>
    {
        hotNews.Add(new HotNews
        {
            Title = x.InnerText,
            Url = x.GetAttributeValue("href", ""),
            SourceId = sourceId,
            CreateTime = DateTime.Now
        });
    });
}
// 36氪
if (item.Source == HotNewsEnum.kr36)
{
    var nodes = ((HtmlDocument)item.Result).DocumentNode.SelectNodes("//div[@class='hotlist-main']/div[@class='hotlist-item-toptwo']/a[2]|//div[@class='hotlist-main']/div[@class='hotlist-item-other clearfloat']/div[@class='hotlist-item-other-info']/a").ToList();
    nodes.ForEach(x =>
    {
        hotNews.Add(new HotNews
        {
            Title = x.InnerText,
            Url = $"https://36kr.com{x.GetAttributeValue("href", "")}",
            SourceId = sourceId,
            CreateTime = DateTime.Now
        });
    });
}
// 百度貼吧
if (item.Source == HotNewsEnum.tieba)
{
    var obj = JObject.Parse(((HtmlDocument)item.Result).ParsedText);
    var nodes = obj["data"]["bang_topic"]["topic_list"];
    foreach (var node in nodes)
    {
        hotNews.Add(new HotNews
        {
            Title = node["topic_name"].ToString(),
            Url = node["topic_url"].ToString().Replace("amp;", ""),
            SourceId = sourceId,
            CreateTime = DateTime.Now
        });
    }
}
// 百度熱搜
if (item.Source == HotNewsEnum.baidu)
{
    var nodes = ((HtmlDocument)item.Result).DocumentNode.SelectNodes("//table[@class='list-table']//tr/td[@class='keyword']/a[@class='list-title']").ToList();
    nodes.ForEach(x =>
    {
        hotNews.Add(new HotNews
        {
            Title = x.InnerText,
            Url = x.GetAttributeValue("href", ""),
            SourceId = sourceId,
            CreateTime = DateTime.Now
        });
    });
}
// 微博熱搜
if (item.Source == HotNewsEnum.weibo)
{
    var nodes = ((HtmlDocument)item.Result).DocumentNode.SelectNodes("//table/tbody/tr/td[2]/a").ToList();
    nodes.ForEach(x =>
    {
        hotNews.Add(new HotNews
        {
            Title = x.InnerText,
            Url = $"https://s.weibo.com{x.GetAttributeValue("href", "").Replace("#", "%23")}",
            SourceId = sourceId,
            CreateTime = DateTime.Now
        });
    });
}
// 知乎熱榜
if (item.Source == HotNewsEnum.zhihu)
{
    var obj = JObject.Parse(((HtmlDocument)item.Result).ParsedText);
    var nodes = obj["data"];
    foreach (var node in nodes)
    {
        hotNews.Add(new HotNews
        {
            Title = node["target"]["title"].ToString(),
            Url = $"https://www.zhihu.com/question/{node["target"]["id"]}",
            SourceId = sourceId,
            CreateTime = DateTime.Now
        });
    }
}
// 知乎日報
if (item.Source == HotNewsEnum.zhihudaily)
{
    var nodes = ((HtmlDocument)item.Result).DocumentNode.SelectNodes("//div[@class='box']/a").ToList();
    nodes.ForEach(x =>
    {
        hotNews.Add(new HotNews
        {
            Title = x.InnerText,
            Url = $"https://daily.zhihu.com{x.GetAttributeValue("href", "")}",
            SourceId = sourceId,
            CreateTime = DateTime.Now
        });
    });
}
// 網易新聞
if (item.Source == HotNewsEnum.news163)
{
    var nodes = ((HtmlDocument)item.Result).DocumentNode.SelectNodes("//div[@class='area-half left']/div[@class='tabBox']/div[@class='tabContents active']/table//tr/td[1]/a").ToList();
    nodes.ForEach(x =>
    {
        hotNews.Add(new HotNews
        {
            Title = x.InnerText,
            Url = x.GetAttributeValue("href", ""),
            SourceId = sourceId,
            CreateTime = DateTime.Now
        });
    });
}
// GitHub
if (item.Source == HotNewsEnum.github)
{
    var nodes = ((HtmlDocument)item.Result).DocumentNode.SelectNodes("//article[@class='Box-row']/h1/a").ToList();
    nodes.ForEach(x =>
    {
        hotNews.Add(new HotNews
        {
            Title = x.InnerText.Trim().Replace("\n", "").Replace(" ", ""),
            Url = $"https://github.com{x.GetAttributeValue("href", "")}",
            SourceId = sourceId,
            CreateTime = DateTime.Now
        });
    });
}
// 抖音熱點
if (item.Source == HotNewsEnum.douyin_hot)
{
    var obj = JObject.Parse(((HtmlDocument)item.Result).ParsedText);
    var nodes = obj["word_list"];
    foreach (var node in nodes)
    {
        hotNews.Add(new HotNews
        {
            Title = node["word"].ToString(),
            Url = $"#{node["hot_value"]}",
            SourceId = sourceId,
            CreateTime = DateTime.Now
        });
    }
}
// 抖音視頻 & 抖音正能量
if (item.Source == HotNewsEnum.douyin_video || item.Source == HotNewsEnum.douyin_positive)
{
    var obj = JObject.Parse(((HtmlDocument)item.Result).ParsedText);
    var nodes = obj["aweme_list"];
    foreach (var node in nodes)
    {
        hotNews.Add(new HotNews
        {
            Title = node["aweme_info"]["desc"].ToString(),
            Url = node["aweme_info"]["share_url"].ToString(),
            SourceId = sourceId,
            CreateTime = DateTime.Now
        });
    }
}

item.Result轉換成指定類型,最終拿到數據后,我們先刪除所有數據后再批量插入。

然後新建擴展方法UseHotNewsJob(),在模塊類中調用。

//MeowvBlogBackgroundJobsExtensions.cs
...
        /// <summary>
        /// 每日熱點數據抓取
        /// </summary>
        /// <param name="context"></param>
        public static void UseHotNewsJob(this IServiceProvider service)
        {
            var job = service.GetService<HotNewsJob>();

            RecurringJob.AddOrUpdate("每日熱點數據抓取", () => job.ExecuteAsync(), CronType.Hour(1, 2));
        }
...

指定定時任務為每2小時運行一次。

...
        public override void OnApplicationInitialization(ApplicationInitializationContext context)
        {
            ...
            var service = context.ServiceProvider;
            ...
            service.UseHotNewsJob();
        }

編譯運行,此時周期性作業就會出現我們的定時任務了。

默認時間沒到是不會執行的,我們手動執行等待一會看看效果。

執行完成后,成功將所有熱點數據保存在數據庫中,說明我們的爬蟲已經搞定了,並且Hangfire會按照給定的規則去循環執行,你學會了嗎?

開源地址:https://github.com/Meowv/Blog/tree/blog_tutorial

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

※回頭車貨運收費標準

宇安交通關係企業,自成立迄今,即秉持著「以誠待人」、「以實處事」的企業信念

分佈式事務_網頁設計公司

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

透過選單樣式的調整、圖片的縮放比例、文字的放大及段落的排版對應來給使用者最佳的瀏覽體驗,所以不用擔心有手機版網站兩個後台的問題,而視覺效果也是透過我們前端設計師優秀的空間比例設計,不會因為畫面變大變小而影響到整體視覺的美感。

 

  
眾所周知,數據庫能實現本地事務,也就是在同一個數據庫中,你可以允許一組操作要麼全都正確執行,要麼全都不執行。這裏特彆強調了本地事務,也就是目前的數據庫只能支持同一個數據庫中的事務。但現在的系統往往採用微服務架構,業務系統擁有獨立的數據庫,因此就出現了跨多個數據庫的事務需求,這種事務即為“分佈式事務”。那麼在目前數據庫不支持跨庫事務的情況下,我們應該如何實現分佈式事務呢?本文首先會為大家梳理分佈式事務的基本概念和理論基礎,然後介紹幾種目前常用的分佈式事務解決方案。廢話不多說,那就開始吧~

1 什麼是事務?

  事務由一組操作構成,我們希望這組操作能夠全部正確執行,如果這一組操作中的任意一個步驟發生錯誤,那麼就需要回滾之前已經完成的操作。也就是同一個事務中的所有操作,要麼全都正確執行,要麼全都不要執行。

2 事務的四大特性 ACID

  說到事務,就不得不提一下事務著名的四大特性。

  • 原子性

      原子性要求,事務是一個不可分割的執行單元,事務中的所有操作要麼全都執行,要麼全都不執行。

  • 一致性

      一致性要求,事務在開始前和結束后,數據庫的完整性約束沒有被破壞。

  • 隔離性

      事務的執行是相互獨立的,它們不會相互干擾,一個事務不會看到另一個正在運行過程中的事務的數據。

  • 持久性

      持久性要求,一個事務完成之後,事務的執行結果必須是持久化保存的。即使數據庫發生崩潰,在數據庫恢復後事務提交的結果仍然不會丟失

 

注意:事務只能保證數據庫的高可靠性,即數據庫本身發生問題后,事務提交后的數據仍然能恢復;而如果不是數據庫本身的故障,如硬盤損壞了,那麼事務提交的數據可能就丟失了。這屬於『高可用性』的範疇。因此,事務只能保證數據庫的『高可靠性』,而『高可用性』需要整個系統共同配合實現。

3 事務的隔離級別

  這裏擴展一下,對事務的隔離性做一個詳細的解釋。

  在事務的四大特性ACID中,要求的隔離性是一種嚴格意義上的隔離,也就是多個事務是串行執行的,彼此之間不會受到任何干擾。這確實能夠完全保證數據的安全性,但在實際業務系統中,這種方式性能不高。因此,數據庫定義了四種隔離級別,隔離級別和數據庫的性能是呈反比的,隔離級別越低,數據庫性能越高,而隔離級別越高,數據庫性能越差。

3.1 事務併發執行會出現的問題

  我們先來看一下在不同的隔離級別下,數據庫可能會出現的問題:

    1. 更新丟失

      當有兩個併發執行的事務,更新同一行數據,那麼有可能一個事務會把另一個事務的更新覆蓋掉。 當數據庫沒有加任何鎖操作的情況下會發生。

    2. 臟讀

      一個事務讀到另一個尚未提交的事務中的數據。 該數據可能會被回滾從而失效。 如果第一個事務拿着失效的數據去處理那就發生錯誤了。

    3. 不可重複讀

      不可重複度的含義:一個事務對同一行數據讀了兩次,卻得到了不同的結果。它具體分為如下兩種情況:

      •   虛讀:在事務1兩次讀取同一記錄的過程中,事務2對該記錄進行了修改,從而事務1第二次讀到了不一樣的記錄。
      •   幻讀:事務1在兩次查詢的過程中,事務2對該表進行了插入、刪除操作,從而事務1第二次查詢的結果發生了變化。

不可重複讀 與 臟讀 的區別? 臟讀讀到的是尚未提交的數據,而不可重複讀讀到的是已經提交的數據,只不過在兩次讀的過程中數據被另一個事務改過了。

3.2 數據庫的四種隔離級別

  數據庫一共有如下四種隔離級別:

  1. Read uncommitted 讀未提交

    在該級別下,一個事務對一行數據修改的過程中,不允許另一個事務對該行數據進行修改,但允許另一個事務對該行數據讀。 因此本級別下,不會出現更新丟失,但會出現臟讀、不可重複讀。

  2. Read committed 讀提交

    在該級別下,未提交的寫事務不允許其他事務訪問該行,因此不會出現臟讀;但是讀取數據的事務允許其他事務的訪問該行數據,因此會出現不可重複讀的情況。

  3. Repeatable read 重複讀

    在該級別下,讀事務禁止寫事務,但允許讀事務,因此不會出現同一事務兩次讀到不同的數據的情況(不可重複讀),且寫事務禁止其他一切事務。

  4. Serializable 序列化

    該級別要求所有事務都必須串行執行,因此能避免一切因併發引起的問題,但效率很低。

  隔離級別越高,越能保證數據的完整性和一致性,但是對併發性能的影響也越大。對於多數應用程序,可以優先考慮把數據庫系統的隔離級別設為Read Committed。它能夠避免臟讀取,而且具有較好的併發性能。儘管它會導致不可重複讀、幻讀和第二類丟失更新這些併發問題,在可能出現這類問題的個別場合,可以由應用程序採用悲觀鎖或樂觀鎖來控制。

4 什麼是分佈式事務?

  到此為止,所介紹的事務都是基於單數據庫的本地事務,目前的數據庫僅支持單庫事務,並不支持跨庫事務。而隨着微服務架構的普及,一個大型業務系統往往由若干個子系統構成,這些子系統又擁有各自獨立的數據庫。往往一個業務流程需要由多個子系統共同完成,而且這些操作可能需要在一個事務中完成。在微服務系統中,這些業務場景是普遍存在的。此時,我們就需要在數據庫之上通過某種手段,實現支持跨數據庫的事務支持,這也就是大家常說的“分佈式事務”。

這裏舉一個分佈式事務的典型例子——用戶下單過程。 當我們的系統採用了微服務架構后,一個電商系統往往被拆分成如下幾個子系統:商品系統、訂單系統、支付系統、積分系統等。整個下單的過程如下:

  1. 用戶通過商品系統瀏覽商品,他看中了某一項商品,便點擊下單
  2. 此時訂單系統會生成一條訂單
  3. 訂單創建成功后,支付系統提供支付功能
  4. 當支付完成后,由積分系統為該用戶增加積分

上述步驟2、3、4需要在一個事務中完成。對於傳統單體應用而言,實現事務非常簡單,只需將這三個步驟放在一個方法A中,再用Spring的@Transactional註解標識該方法即可。Spring通過數據庫的事務支持,保證這些步驟要麼全都執行完成,要麼全都不執行。但在這個微服務架構中,這三個步驟涉及三個系統,涉及三個數據庫,此時我們必須在數據庫和應用系統之間,通過某項黑科技,實現分佈式事務的支持。

5 CAP理論

CAP理論說的是:在一個分佈式系統中,最多只能滿足C、A、P中的兩個需求。

CAP的含義:

  • C:Consistency 一致性

    同一數據的多個副本是否實時相同。

  • A:Availability 可用性

    可用性:一定時間內 & 系統返回一個明確的結果 則稱為該系統可用。

  • P:Partition tolerance 分區容錯性

    將同一服務分佈在多個系統中,從而保證某一個系統宕機,仍然有其他系統提供相同的服務。

  CAP理論告訴我們,在分佈式系統中,C、A、P三個條件中我們最多只能選擇兩個。那麼問題來了,究竟選擇哪兩個條件較為合適呢?

對於一個業務系統來說,可用性和分區容錯性是必須要滿足的兩個條件,並且這兩者是相輔相成的。業務系統之所以使用分佈式系統,主要原因有兩個:

  • 提升整體性能

    當業務量猛增,單個服務器已經無法滿足我們的業務需求的時候,就需要使用分佈式系統,使用多個節點提供相同的功能,從而整體上提升系統的性能,這就是使用分佈式系統的第一個原因。

  • 實現分區容錯性

    單一節點 或 多個節點處於相同的網絡環境下,那麼會存在一定的風險,萬一該機房斷電、該地區發生自然災害,那麼業務系統就全面癱瘓了。為了防止這一問題,採用分佈式系統,將多個子系統分佈在不同的地域、不同的機房中,從而保證系統高可用性。

  這說明分區容錯性是分佈式系統的根本,如果分區容錯性不能滿足,那使用分佈式系統將失去意義。

  此外,可用性對業務系統也尤為重要。在大談用戶體驗的今天,如果業務系統時常出現“系統異常”、響應時間過長等情況,這使得用戶對系統的好感度大打折扣,在互聯網行業競爭激烈的今天,相同領域的競爭者不甚枚舉,系統的間歇性不可用會立馬導致用戶流向競爭對手。因此,我們只能通過犧牲一致性來換取系統的可用性分區容錯性。這也就是下面要介紹的BASE理論。

6 BASE理論

  CAP理論告訴我們一個悲慘但不得不接受的事實——我們只能在C、A、P中選擇兩個條件。而對於業務系統而言,我們往往選擇犧牲一致性來換取系統的可用性和分區容錯性。不過這裏要指出的是,所謂的“犧牲一致性”並不是完全放棄數據一致性,而是犧牲強一致性換取弱一致性。下面來介紹下BASE理論。

  • BA:Basic Available 基本可用S:Soft State:柔性狀態 同一數據的不同副本的狀態,可以不需要實時一致。
    • 整個系統在某些不可抗力的情況下,仍然能夠保證“可用性”,即一定時間內仍然能夠返回一個明確的結果。只不過“基本可用”和“高可用”的區別是:
      • “一定時間”可以適當延長 當舉行大促時,響應時間可以適當延長
      • 給部分用戶返回一個降級頁面 給部分用戶直接返回一個降級頁面,從而緩解服務器壓力。但要注意,返回降級頁面仍然是返回明確結果。
  • E:Eventual Consisstency:最終一致性 同一數據的不同副本的狀態,可以不需要實時一致,但一定要保證經過一定時間后仍然是一致的。

7 酸鹼平衡

  ACID能夠保證事務的強一致性,即數據是實時一致的。這在本地事務中是沒有問題的,在分佈式事務中,強一致性會極大影響分佈式系統的性能,因此分佈式系統中遵循BASE理論即可。但分佈式系統的不同業務場景對一致性的要求也不同。如交易場景下,就要求強一致性,此時就需要遵循ACID理論,而在註冊成功后發送短信驗證碼等場景下,並不需要實時一致,因此遵循BASE理論即可。因此要根據具體業務場景,在ACID和BASE之間尋求平衡。

8 分佈式事務協議

  下面介紹幾種實現分佈式事務的協議。

8.1 兩階段提交協議 2PC

分佈式系統的一個難點是如何保證架構下多個節點在進行事務性操作的時候保持一致性。為實現這個目的,二階段提交算法的成立基於以下假設:

  • 該分佈式系統中,存在一個節點作為協調者(Coordinator),其他節點作為參与者(Cohorts)。且節點之間可以進行網絡通信。
  • 所有節點都採用預寫式日誌,且日誌被寫入后即被保持在可靠的存儲設備上,即使節點損壞不會導致日誌數據的消失。
  • 所有節點不會永久性損壞,即使損壞后仍然可以恢復。

1. 第一階段(投票階段):

  1. 協調者節點向所有參与者節點詢問是否可以執行提交操作(vote),並開始等待各參与者節點的響應。
  2. 參与者節點執行詢問發起為止的所有事務操作,並將Undo信息和Redo信息寫入日誌。(注意:若成功這裏其實每個參与者已經執行了事務操作)
  3. 各參与者節點響應協調者節點發起的詢問。如果參与者節點的事務操作實際執行成功,則它返回一個”同意”消息;如果參与者節點的事務操作實際執行失敗,則它返回一個”中止”消息。

2. 第二階段(提交執行階段):

  當協調者節點從所有參与者節點獲得的相應消息都為”同意”時:

  1. 協調者節點向所有參与者節點發出”正式提交(commit)”的請求。
  2. 參与者節點正式完成操作,並釋放在整個事務期間內佔用的資源。
  3. 參与者節點向協調者節點發送”完成”消息。
  4. 協調者節點受到所有參与者節點反饋的”完成”消息后,完成事務。

  如果任一參与者節點在第一階段返回的響應消息為”中止”,或者 協調者節點在第一階段的詢問超時之前無法獲取所有參与者節點的響應消息時:

  1. 協調者節點向所有參与者節點發出”回滾操作(rollback)”的請求。
  2. 參与者節點利用之前寫入的Undo信息執行回滾,並釋放在整個事務期間內佔用的資源。
  3. 參与者節點向協調者節點發送”回滾完成”消息。
  4. 協調者節點受到所有參与者節點反饋的”回滾完成”消息后,取消事務。

  不管最後結果如何,第二階段都會結束當前事務。

  二階段提交看起來確實能夠提供原子性的操作,但是不幸的事,二階段提交還是有幾個缺點的:

  1. 執行過程中,所有參与節點都是事務阻塞型的。當參与者佔有公共資源時,其他第三方節點訪問公共資源不得不處於阻塞狀態。
  2. 參与者發生故障。協調者需要給每個參与者額外指定超時機制,超時后整個事務失敗。(沒有多少容錯機制)
  3. 協調者發生故障。參与者會一直阻塞下去。需要額外的備機進行容錯。(這個可以依賴後面要講的Paxos協議實現HA)
  4. 二階段無法解決的問題:協調者再發出commit消息之後宕機,而唯一接收到這條消息的參与者同時也宕機了。那麼即使協調者通過選舉協議產生了新的協調者,這條事務的狀態也是不確定的,沒人知道事務是否被已經提交。

為此,Dale Skeen和Michael Stonebraker在“A Formal Model of Crash Recovery in a Distributed System”中提出了三階段提交協議(3PC)。

8.2 三階段提交協議 3PC

  與兩階段提交不同的是,三階段提交有兩個改動點。

  • 引入超時機制。同時在協調者和參与者中都引入超時機制。
  • 在第一階段和第二階段中插入一個準備階段。保證了在最後提交階段之前各參与節點的狀態是一致的。

  也就是說,除了引入超時機制之外,3PC把2PC的準備階段再次一分為二,這樣三階段提交就有CanCommit、PreCommit、DoCommit三個階段。

1. CanCommit階段

  3PC的CanCommit階段其實和2PC的準備階段很像。協調者向參与者發送commit請求,參与者如果可以提交就返回Yes響應,否則返回No響應。

  1. 事務詢問

    協調者向參与者發送CanCommit請求。詢問是否可以執行事務提交操作。然後開始等待參与者的響應。

  2. 響應反饋

    參与者接到CanCommit請求之後,正常情況下,如果其自身認為可以順利執行事務,則返回Yes響應,並進入預備狀態。否則反饋No

2. PreCommit階段

  協調者根據參与者的反應情況來決定是否可以記性事務的PreCommit操作。根據響應情況,有以下兩種可能。 假如協調者從所有的參与者獲得的反饋都是Yes響應,那麼就會執行事務的預執行。

  1. 發送預提交請求

    協調者向參与者發送PreCommit請求,並進入Prepared階段。

  2. 事務預提交

    參与者接收到PreCommit請求后,會執行事務操作,並將undo和redo信息記錄到事務日誌中。

  3. 響應反饋

    如果參与者成功的執行了事務操作,則返回ACK響應,同時開始等待最終指令。

假如有任何一個參与者向協調者發送了No響應,或者等待超時之後,協調者都沒有接到參与者的響應,那麼就執行事務的中斷。

  1. 發送中斷請求

    協調者向所有參与者發送abort請求。

  2. 中斷事務

    參与者收到來自協調者的abort請求之後(或超時之後,仍未收到協調者的請求),執行事務的中斷。

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

    搬家費用:依消費者運送距離、搬運樓層、有無電梯、步行距離、特殊地形、超重物品等計價因素後,評估每車次單

3. doCommit階段 該階段進行真正的事務提交,也可以分為以下兩種情況。

  該階段進行真正的事務提交,也可以分為以下兩種情況。

3.1 執行提交

  1. 發送提交請求

    協調接收到參与者發送的ACK響應,那麼他將從預提交狀態進入到提交狀態。並向所有參与者發送doCommit請求。

  2. 事務提交

    參与者接收到doCommit請求之後,執行正式的事務提交。並在完成事務提交之後釋放所有事務資源。

  3. 響應反饋

    事務提交完之後,向協調者發送Ack響應。

  4. 完成事務

    協調者接收到所有參与者的ack響應之後,完成事務。

3.2 中斷事務 協調者沒有接收到參与者發送的ACK響應(可能是接受者發送的不是ACK響應,也可能響應超時),那麼就會執行中斷事務。

  1. 發送中斷請求

    協調者向所有參与者發送abort請求

  2. 事務回滾

    參与者接收到abort請求之後,利用其在階段二記錄的undo信息來執行事務的回滾操作,並在完成回滾之後釋放所有的事務資源。

  3. 反饋結果

    參与者完成事務回滾之後,向協調者發送ACK消息

  4. 中斷事務

    協調者接收到參与者反饋的ACK消息之後,執行事務的中斷。

9 分佈式事務的解決方案

  分佈式事務的解決方案有如下幾種:

  • 全局消息
  • 基於可靠消息服務的分佈式事務
  • TCC
  • 最大努力通知

9.1 方案1:全局事務(DTP模型)

  全局事務基於DTP模型實現。DTP是由X/Open組織提出的一種分佈式事務模型——X/Open Distributed Transaction Processing Reference Model。它規定了要實現分佈式事務,需要三種角色:

  • AP:Application 應用系統

    它就是我們開發的業務系統,在我們開發的過程中,可以使用資源管理器提供的事務接口來實現分佈式事務。

  • TM:Transaction Manager 事務管理器

    • 分佈式事務的實現由事務管理器來完成,它會提供分佈式事務的操作接口供我們的業務系統調用。這些接口稱為TX接口。
    • 事務管理器還管理着所有的資源管理器,通過它們提供的XA接口來同一調度這些資源管理器,以實現分佈式事務。
    • DTP只是一套實現分佈式事務的規範,並沒有定義具體如何實現分佈式事務,TM可以採用2PC、3PC、Paxos等協議實現分佈式事務。
  • RM:Resource Manager 資源管理器

    • 能夠提供數據服務的對象都可以是資源管理器,比如:數據庫、消息中間件、緩存等。大部分場景下,數據庫即為分佈式事務中的資源管理器。
    • 資源管理器能夠提供單數據庫的事務能力,它們通過XA接口,將本數據庫的提交、回滾等能力提供給事務管理器調用,以幫助事務管理器實現分佈式的事務管理。
    • XA是DTP模型定義的接口,用於向事務管理器提供該資源管理器(該數據庫)的提交、回滾等能力。
    • DTP只是一套實現分佈式事務的規範,RM具體的實現是由數據庫廠商來完成的。
  1. 有沒有基於DTP模型的分佈式事務中間件?
  1. DTP模型有啥優缺點?

9.2 方案2:基於可靠消息服務的分佈式事務

  這種實現分佈式事務的方式需要通過消息中間件來實現。假設有A和B兩個系統,分別可以處理任務A和任務B。此時系統A中存在一個業務流程,需要將任務A和任務B在同一個事務中處理。下面來介紹基於消息中間件來實現這種分佈式事務。

 

 

  • 在系統A處理任務A前,首先向消息中間件發送一條消息
  • 消息中間件收到后將該條消息持久化,但並不投遞。此時下游系統B仍然不知道該條消息的存在。
  • 消息中間件持久化成功后,便向系統A返回一個確認應答;
  • 系統A收到確認應答后,則可以開始處理任務A;
  • 任務A處理完成后,向消息中間件發送Commit請求。該請求發送完成后,對系統A而言,該事務的處理過程就結束了,此時它可以處理別的任務了。 但commit消息可能會在傳輸途中丟失,從而消息中間件並不會向系統B投遞這條消息,從而系統就會出現不一致性。這個問題由消息中間件的事務回查機制完成,下文會介紹。
  • 消息中間件收到Commit指令后,便向系統B投遞該消息,從而觸發任務B的執行;
  • 當任務B執行完成后,系統B向消息中間件返回一個確認應答,告訴消息中間件該消息已經成功消費,此時,這個分佈式事務完成。

上述過程可以得出如下幾個結論:

  1. 消息中間件扮演者分佈式事務協調者的角色。
  2. 系統A完成任務A后,到任務B執行完成之間,會存在一定的時間差。在這個時間差內,整個系統處於數據不一致的狀態,但這短暫的不一致性是可以接受的,因為經過短暫的時間后,系統又可以保持數據一致性,滿足BASE理論。

上述過程中,如果任務A處理失敗,那麼需要進入回滾流程,如下圖所示:

 

  • 若系統A在處理任務A時失敗,那麼就會向消息中間件發送Rollback請求。和發送Commit請求一樣,系統A發完之後便可以認為回滾已經完成,它便可以去做其他的事情。

  • 消息中間件收到回滾請求后,直接將該消息丟棄,而不投遞給系統B,從而不會觸發系統B的任務B。

此時系統又處於一致性狀態,因為任務A和任務B都沒有執行。

  上面所介紹的Commit和Rollback都屬於理想情況,但在實際系統中,Commit和Rollback指令都有可能在傳輸途中丟失。那麼當出現這種情況的時候,消息中間件是如何保證數據一致性呢?——答案就是超時詢問機制。

 

 

  系統A除了實現正常的業務流程外,還需提供一個事務詢問的接口,供消息中間件調用。當消息中間件收到一條事務型消息后便開始計時,如果到了超時時間也沒收到系統A發來的Commit或Rollback指令的話,就會主動調用系統A提供的事務詢問接口詢問該系統目前的狀態。該接口會返回三種結果:

  • 提交

    若獲得的狀態是“提交”,則將該消息投遞給系統B。

  • 回滾

    若獲得的狀態是“回滾”,則直接將條消息丟棄。

  • 處理中

    若獲得的狀態是“處理中”,則繼續等待。

消息中間件的超時詢問機制能夠防止上游系統因在傳輸過程中丟失Commit/Rollback指令而導致的系統不一致情況,而且能降低上游系統的阻塞時間,上游系統只要發出Commit/Rollback指令后便可以處理其他任務,無需等待確認應答。而Commit/Rollback指令丟失的情況通過超時詢問機制來彌補,這樣大大降低上游系統的阻塞時間,提升系統的併發度。

  下面來說一說消息投遞過程的可靠性保證。 當上游系統執行完任務並向消息中間件提交了Commit指令后,便可以處理其他任務了,此時它可以認為事務已經完成,接下來消息中間件**一定會保證消息被下游系統成功消費掉!**那麼這是怎麼做到的呢?這由消息中間件的投遞流程來保證。

  消息中間件向下游系統投遞完消息后便進入阻塞等待狀態,下游系統便立即進行任務的處理,任務處理完成后便向消息中間件返回應答。消息中間件收到確認應答后便認為該事務處理完畢!

  如果消息在投遞過程中丟失,或消息的確認應答在返回途中丟失,那麼消息中間件在等待確認應答超時之後就會重新投遞,直到下游消費者返回消費成功響應為止。當然,一般消息中間件可以設置消息重試的次數和時間間隔,比如:當第一次投遞失敗后,每隔五分鐘重試一次,一共重試3次。如果重試3次之後仍然投遞失敗,那麼這條消息就需要人工干預。

 

 

 

有的同學可能要問:消息投遞失敗後為什麼不回滾消息,而是不斷嘗試重新投遞?

這就涉及到整套分佈式事務系統的實現成本問題。 我們知道,當系統A將向消息中間件發送Commit指令后,它便去做別的事情了。如果此時消息投遞失敗,需要回滾的話,就需要讓系統A事先提供回滾接口,這無疑增加了額外的開發成本,業務系統的複雜度也將提高。對於一個業務系統的設計目標是,在保證性能的前提下,最大限度地降低系統複雜度,從而能夠降低系統的運維成本。

不知大家是否發現,上游系統A向消息中間件提交Commit/Rollback消息採用的是異步方式,也就是當上游系統提交完消息后便可以去做別的事情,接下來提交、回滾就完全交給消息中間件來完成,並且完全信任消息中間件,認為它一定能正確地完成事務的提交或回滾。然而,消息中間件向下游系統投遞消息的過程是同步的。也就是消息中間件將消息投遞給下游系統后,它會阻塞等待,等下游系統成功處理完任務返回確認應答后才取消阻塞等待。為什麼這兩者在設計上是不一致的呢?

  首先,上游系統和消息中間件之間採用異步通信是為了提高系統併發度。業務系統直接和用戶打交道,用戶體驗尤為重要,因此這種異步通信方式能夠極大程度地降低用戶等待時間。此外,異步通信相對於同步通信而言,沒有了長時間的阻塞等待,因此系統的併發性也大大增加。但異步通信可能會引起Commit/Rollback指令丟失的問題,這就由消息中間件的超時詢問機制來彌補。

  那麼,消息中間件和下游系統之間為什麼要採用同步通信呢?

  異步能提升系統性能,但隨之會增加系統複雜度;而同步雖然降低系統併發度,但實現成本較低。因此,在對併發度要求不是很高的情況下,或者服務器資源較為充裕的情況下,我們可以選擇同步來降低系統的複雜度。 我們知道,消息中間件是一個獨立於業務系統的第三方中間件,它不和任何業務系統產生直接的耦合,它也不和用戶產生直接的關聯,它一般部署在獨立的服務器集群上,具有良好的可擴展性,所以不必太過於擔心它的性能,如果處理速度無法滿足我們的要求,可以增加機器來解決。而且,即使消息中間件處理速度有一定的延遲那也是可以接受的,因為前面所介紹的BASE理論就告訴我們了,我們追求的是最終一致性,而非實時一致性,因此消息中間件產生的時延導致事務短暫的不一致是可以接受的。

9.3 方案3:最大努力通知(定期校對)

  最大努力通知也被稱為定期校對,其實在方案二中已經包含,這裏再單獨介紹,主要是為了知識體系的完整性。這種方案也需要消息中間件的參与,其過程如下:

 

 

  • 上游系統在完成任務后,向消息中間件同步地發送一條消息,確保消息中間件成功持久化這條消息,然後上游系統可以去做別的事情了;
  • 消息中間件收到消息后負責將該消息同步投遞給相應的下游系統,並觸發下游系統的任務執行;
  • 當下游系統處理成功后,向消息中間件反饋確認應答,消息中間件便可以將該條消息刪除,從而該事務完成。

上面是一個理想化的過程,但在實際場景中,往往會出現如下幾種意外情況:

  1. 消息中間件向下游系統投遞消息失敗
  2. 上游系統向消息中間件發送消息失敗

  對於第一種情況,消息中間件具有重試機制,我們可以在消息中間件中設置消息的重試次數和重試時間間隔,對於網絡不穩定導致的消息投遞失敗的情況,往往重試幾次后消息便可以成功投遞,如果超過了重試的上限仍然投遞失敗,那麼消息中間件不再投遞該消息,而是記錄在失敗消息表中,消息中間件需要提供失敗消息的查詢接口,下游系統會定期查詢失敗消息,並將其消費,這就是所謂的“定期校對”。

如果重複投遞和定期校對都不能解決問題,往往是因為下游系統出現了嚴重的錯誤,此時就需要人工干預。

  對於第二種情況,需要在上游系統中建立消息重發機制。可以在上游系統建立一張本地消息表,並將 任務處理過程向本地消息表中插入消息 這兩個步驟放在一個本地事務中完成。如果向本地消息表插入消息失敗,那麼就會觸發回滾,之前的任務處理結果就會被取消。如果這量步都執行成功,那麼該本地事務就完成了。接下來會有一個專門的消息發送者不斷地發送本地消息表中的消息,如果發送失敗它會返回重試。當然,也要給消息發送者設置重試的上限,一般而言,達到重試上限仍然發送失敗,那就意味着消息中間件出現嚴重的問題,此時也只有人工干預才能解決問題。

  對於不支持事務型消息的消息中間件,如果要實現分佈式事務的話,就可以採用這種方式。它能夠通過重試機制+定期校對實現分佈式事務,但相比於第二種方案,它達到數據一致性的周期較長,而且還需要在上游系統中實現消息重試發布機制,以確保消息成功發布給消息中間件,這無疑增加了業務系統的開發成本,使得業務系統不夠純粹,並且這些額外的業務邏輯無疑會佔用業務系統的硬件資源,從而影響性能。

因此,盡量選擇支持事務型消息的消息中間件來實現分佈式事務,如RocketMQ。

9.4 方案4:TCC(兩階段型、補償型)

  TCC即為Try Confirm Cancel,它屬於補償型分佈式事務。顧名思義,TCC實現分佈式事務一共有三個步驟:

  • Try:嘗試待執行的業務
    • 這個過程並未執行業務,只是完成所有業務的一致性檢查,並預留好執行所需的全部資源
  • Confirm:執行業務
    • 這個過程真正開始執行業務,由於Try階段已經完成了一致性檢查,因此本過程直接執行,而不做任何檢查。並且在執行的過程中,會使用到Try階段預留的業務資源。
  • Cancel:取消執行的業務
    • 若業務執行失敗,則進入Cancel階段,它會釋放所有佔用的業務資源,並回滾Confirm階段執行的操作。

下面以一個轉賬的例子來解釋下TCC實現分佈式事務的過程。

假設用戶A用他的賬戶餘額給用戶B發一個100元的紅包,並且餘額系統和紅包系統是兩個獨立的系統。

  • Try

    • 創建一條轉賬流水,並將流水的狀態設為交易中
    • 將用戶A的賬戶中扣除100元(預留業務資源)
    • Try成功之後,便進入Confirm階段
    • Try過程發生任何異常,均進入Cancel階段
  • Confirm

    • 向B用戶的紅包賬戶中增加100元
    • 將流水的狀態設為交易已完成
    • Confirm過程發生任何異常,均進入Cancel階段
    • Confirm過程執行成功,則該事務結束
  • Cancel

    • 將用戶A的賬戶增加100元
    • 將流水的狀態設為交易失敗

  在傳統事務機制中,業務邏輯的執行和事務的處理,是在不同的階段由不同的部件來完成的:業務邏輯部分訪問資源實現數據存儲,其處理是由業務系統負責;事務處理部分通過協調資源管理器以實現事務管理,其處理由事務管理器來負責。二者沒有太多交互的地方,所以,傳統事務管理器的事務處理邏輯,僅需要着眼於事務完成(commit/rollback)階段,而不必關注業務執行階段。

TCC全局事務必須基於RM本地事務來實現全局事務

  TCC服務是由Try/Confirm/Cancel業務構成的, 其Try/Confirm/Cancel業務在執行時,會訪問資源管理器(Resource Manager,下文簡稱RM)來存取數據。這些存取操作,必須要參与RM本地事務,以使其更改的數據要麼都commit,要麼都rollback。

這一點不難理解,考慮一下如下場景:

 

 

  假設圖中的服務B沒有基於RM本地事務(以RDBS為例,可通過設置auto-commit為true來模擬),那麼一旦[B:Try]操作中途執行失敗,TCC事務框架後續決定回滾全局事務時,該[B:Cancel]則需要判斷[B:Try]中哪些操作已經寫到DB、哪些操作還沒有寫到DB:假設[B:Try]業務有5個寫庫操作,[B:Cancel]業務則需要逐個判斷這5個操作是否生效,並將生效的操作執行反向操作。

  不幸的是,由於[B:Cancel]業務也有n(0<=n<=5)個反向的寫庫操作,此時一旦[B:Cancel]也中途出錯,則後續的[B:Cancel]執行任務更加繁重。因為,相比第一次[B:Cancel]操作,後續的[B:Cancel]操作還需要判斷先前的[B:Cancel]操作的n(0<=n<=5)個寫庫中哪幾個已經執行、哪幾個還沒有執行,這就涉及到了冪等性問題。而對冪等性的保障,又很可能還需要涉及額外的寫庫操作,該寫庫操作又會因為沒有RM本地事務的支持而存在類似問題。。。可想而知,如果不基於RM本地事務,TCC事務框架是無法有效的管理TCC全局事務的。

反之,基於RM本地事務的TCC事務,這種情況則會很容易處理:[B:Try]操作中途執行失敗,TCC事務框架將其參与RM本地事務直接rollback即可。後續TCC事務框架決定回滾全局事務時,在知道“[B:Try]操作涉及的RM本地事務已經rollback”的情況下,根本無需執行[B:Cancel]操作。

換句話說,基於RM本地事務實現TCC事務框架時,一個TCC型服務的cancel業務要麼執行,要麼不執行,不需要考慮部分執行的情況。

TCC事務框架應該提供Confirm/Cancel服務的冪等性保障

  一般認為,服務的冪等性,是指針對同一個服務的多次(n>1)請求和對它的單次(n=1)請求,二者具有相同的副作用。

在TCC事務模型中,Confirm/Cancel業務可能會被重複調用,其原因很多。比如,全局事務在提交/回滾時會調用各TCC服務的Confirm/Cancel業務邏輯。執行這些Confirm/Cancel業務時,可能會出現如網絡中斷的故障而使得全局事務不能完成。因此,故障恢復機制後續仍然會重新提交/回滾這些未完成的全局事務,這樣就會再次調用參与該全局事務的各TCC服務的Confirm/Cancel業務邏輯。

  既然Confirm/Cancel業務可能會被多次調用,就需要保障其冪等性。 那麼,應該由TCC事務框架來提供冪等性保障?還是應該由業務系統自行來保障冪等性呢? 個人認為,應該是由TCC事務框架來提供冪等性保障。如果僅僅只是極個別服務存在這個問題的話,那麼由業務系統來負責也是可以的;然而,這是一類公共問題,毫無疑問,所有TCC服務的Confirm/Cancel業務存在冪等性問題。TCC服務的公共問題應該由TCC事務框架來解決;而且,考慮一下由業務系統來負責冪等性需要考慮的問題,就會發現,這無疑增大了業務系統的複雜度。


作者:大閑人柴毛毛


鏈接:https://juejin.im/post/5ab0d1a3f265da23731448e0


來源:掘金


著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

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

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

節能減碳愛地球是景泰電動車的理念,是創立景泰電動車行的初衷,滿意態度更是服務客戶的最高品質,我們的成長來自於你的推薦。

13萬落地 你可以買到質量好又省心的合資家轎有哪些?_貨運

※評比南投搬家公司費用收費行情懶人包大公開

搬家價格與搬家費用透明合理,不亂收費。本公司提供下列三種搬家計費方案,由資深專業組長到府估價,替客戶量身規劃選擇最經濟節省的計費方式

雖然新款寶來相比老款寶來的長度有所增長,但是和同級別比起來,還是沒有什麼優勢。寶來的動力系統和朗逸比起來要精簡很多,發動機為1。6L 110馬力和1。4T 131馬力,匹配5擋手動、6擋手自一體和7擋雙離合變速箱。和桑塔納速騰一樣,寶來的後排地板中部凸起挺高的,影響舒適性。

今天給大家推薦三個高質量低油耗的合資緊湊型轎車,性價比都是很高的。

北京現代-領動

領動的車身尺寸為4610*1800*1450mm,軸距為2700mm,領動目前是現代集團最新的緊湊型轎車,也就是朗動的換代車型,這也是現代最喜歡的的多代同堂銷售。因為是最新款,所以領動的也是採用了最新的設計語言,同時加上自己是韓國車,所以顏值也是力壓全場。但從外觀上,領動已經可以征服很大一部分消費者了。

領動的動力系統為1.6L 128馬力 L、1.6L 130馬力 L4和1.4T 130馬力 L4,匹配6擋手動、6擋手自一體變速箱和7擋雙離合。其中領動的自動擋車型都採用了直噴發動機,動力輸出會更暢快,油耗也會相應的降低一點。其中1.6L自動擋車型的百公里油耗為7L多一點,表現很不錯。

不過遺憾的是領動的最低配依然沒有配備ESp車身穩定系統。前幾天一個朋友剛提了朗動,我說為啥不買領動呢?他說,朗動可以優惠2.5萬,領動可以么?突然感覺他說的好有道理,

※回頭車貨運收費標準

宇安交通關係企業,自成立迄今,即秉持著「以誠待人」、「以實處事」的企業信念

我竟無言以對。所以希望領動的優惠也能大一點。

一汽大眾-寶來

寶來的車身尺寸為4562*1793*1468mm,軸距為2614mm。雖然新款寶來相比老款寶來的長度有所增長,但是和同級別比起來,還是沒有什麼優勢。

寶來的動力系統和朗逸比起來要精簡很多,發動機為1.6L 110馬力和1.4T 131馬力,匹配5擋手動、6擋手自一體和7擋雙離合變速箱。和桑塔納速騰一樣,寶來的後排地板中部凸起挺高的,影響舒適性。不過寶來的口碑也是路人皆知的,耐用省油一直是大家對它的印象。小編一直覺得,如果寶來不是掛着大眾的標,最起碼還能再優惠一萬。

廣汽本田-凌派

凌派車身尺寸為4664*1750*1505mm,軸距是2650mm。雖然軸距沒達到2.7米,但是本田的偷空間技術也是赫赫有名的,同時作為中國特供車的身份,兩個因素加起來使得凌派的後排空間十分巨大,另外後排地板也比較平整,就算後排坐三個人也不會感到不舒服。

凌派的發動機為1.8L 136馬力 L4,變速箱為5擋手動和CVT變速箱。雖然1.8L不能參与購置稅減半的政策優惠,但是這套動力系統也是久經考驗,成熟耐用,另外凌派有換上了一套CVT變速箱,雖然是1.8L的排量,但是自動擋的百公里油耗只有7.7L左右。另外這套動力系統也是非常的平順。

最後,凌派全系標配剎車輔助、牽引力控制、ESp。作為日系車,這點還是比較實在的。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

※智慧手機時代的來臨,RWD網頁設計為架站首選

網動結合了許多網際網路業界的菁英共同研發簡單易操作的架站工具,及時性的更新,為客戶創造出更多的網路商機。

6萬以內最高顏值,這款合資小車妹子看到絕對會被帥暈_網頁設計公司

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

搬家費用:依消費者運送距離、搬運樓層、有無電梯、步行距離、特殊地形、超重物品等計價因素後,評估每車次單

飛鏢型的尾燈相信大家都不會陌生,內部依然是普通的鹵素燈泡,根據它的定位以及價格依然在情理之內。尾部造型和日產騏達又或者說是奔馳GLA非常的類似,輪拱外拋做出了一種寬體的感覺,運動感就體現出來了,而且保險杠採用了三色的設計,增添了一點活力。

日產瑪馳

近年來,日產在設計上採用年輕激進的設計,其中在藍鳥、騏達、天籟、西瑪這些車型由其明顯,非常的運動激進。而在最近巴黎車展中,日產把這種設計理念帶到了旗下的瑪馳上,這原本小巧可愛的小型車又會變成怎樣呢?

在外觀上,新一代瑪馳幾乎看不出上一代瑪馳的影子,車身尺寸稍微增長,但依然是處於小型車的範疇,車身線條從之前的圓潤變成現在的稜角分明,就像是一個小正太去健身房去健身回來,長了一身的肌肉。

採用日產最新的V-motion設計理念,車頭設計非常激進動感,

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

節能減碳愛地球是景泰電動車的理念,是創立景泰電動車行的初衷,滿意態度更是服務客戶的最高品質,我們的成長來自於你的推薦。

整體給人的感覺就像是一輛縮小版的日產藍鳥,更像是“憤怒的小鳥”,而且保險杠還有着前鏟的設計,平添了一些性能味,比起藍鳥還要動感。

車燈角位集成了“><”樣式的日間行車燈,非常的可愛。而且採用遠近光一體式前大燈,集成了透鏡,不知道這是否高配的專屬。

雙色的鋁合金輪轂和車身非常般配。

受制於車身尺寸,在長度上就別想着新一代瑪馳能有修長的車身線條了。在側面我們還可以看出這採用了溜背造型以及懸浮式車頂設計,相信能俘掠不少年輕人的心。

飛鏢型的尾燈相信大家都不會陌生,內部依然是普通的鹵素燈泡,根據它的定位以及價格依然在情理之內。

尾部造型和日產騏達又或者說是奔馳GLA非常的類似,輪拱外拋做出了一種寬體的感覺,運動感就體現出來了,而且保險杠採用了三色的設計,增添了一點活力。

內飾方面採用了大膽的多色拼接設計,極具活力。其次採用的是性能車多使用的D型方向盤。

動力方面,新一代瑪馳將會採用0.9T渦輪增壓發動機或者是1.0L自然吸氣發動機。價格方面極有可能維持原來的5.5-8萬的售價,在引進中國以後它或將成為該價位顏值的最高合資車,能受到不少年輕人追捧。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

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

透過選單樣式的調整、圖片的縮放比例、文字的放大及段落的排版對應來給使用者最佳的瀏覽體驗,所以不用擔心有手機版網站兩個後台的問題,而視覺效果也是透過我們前端設計師優秀的空間比例設計,不會因為畫面變大變小而影響到整體視覺的美感。

北京匡時2019秋拍大戲開啟 中國書畫專場名家名作薈萃_包裝設計

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

上新台中搬家公司提供您一套專業有效率且人性化的辦公室搬遷、公司行號搬家及工廠遷廠的搬家服務

北京匡時2019秋季拍賣會於21日在北京拉開帷幕,本季秋拍集中呈現了中國書畫、古籍善本、國石篆刻、名硯、現當代藝術等多個門類的600餘件藝術臻品,不僅有流傳有緒的名品——齊白石、陳半丁合繪《挖耳圖》,也有唐宋寫經、宋元碑帖等精品古籍刻本。

“中國書畫專場”共300餘件拍品,其中尤其值得關注的拍品有齊白石、黃賓虹、張大千、溥儒、林風眠、高鳳翰、錢泳、屠倬以及顧大昌舊藏清中拓《開通褒斜道摩崖》、初拓《袁安碑》等。北京文物商店舊藏齊白石、陳半丁1945年合繪的《挖耳圖》,此作先後在1990年在日本大阪龍華堂、新加坡新華美術中心以及首都博物館展出,有六次出版,是一件流傳有緒的赫赫名作。

另有新加坡戴淮清、唐錦雲夫婦舊藏的齊白石《燭鼠圖》《紅梅圖》,都是直接得自新加坡中華書局。黃賓虹《贈蔡守、談月色山水》手卷是為南社同仁所作,以用筆為主,勁峭簡練,有別於後期的層層點染,更多一些清朴自在的風味。

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

窩窩觸角包含自媒體、自有平台及其他國家營銷業務等,多角化經營並具有國際觀的永續理念。

溥儒《鍾馗圖》用溥氏獨有的精準爽利的釘頭鼠尾描將鍾馗袍帶寬厚鬆軟的質感表現的淋漓盡致,也在衣折的蜿蜒承轉之中將人物的動態表現的入木三分致。林風眠《三美圖》畫的是一種半具體半抽象的環境,營造一種朦朧的情調,由姿情和氣質傳達出東方女性的溫柔閑雅、清淡秀媚、如詩如夢。

古籍善本專場則甄選拍品207件,包括唐宋寫經、宋元及歷代刻經等佛教經典、宮廷典籍、明清珍本、金石碑帖、拿破侖王后及陳寅恪舊藏西文搖籃本等諸多專題,其中由同一藏家提供的29件拍品多為稀見刻本及批校本,如蒲學泰斗路大荒手校《聊齋誌異》、鍾佛操精校《古文詞略》、劉世珩校樣本《大戴禮記》等,並徵得周叔弢家族的校刻本及藏書等。

方寸乾坤——印石篆刻專場彙集了明清代以來諸多文人篆刻精品佳作,這些印章有篆刻名家自用印,也有其為友人所刻姓名印、收藏印、詩文閑章等。所涉名家包括程邃、楊玉璇、郭懋介、郭祥忍、郭祥雄、陳禮忠、潘驚石、張純連等,可謂名家薈萃。

此外,預展還有寶硯齋藏硯專場、現當代藝術專場等精品。預展期為12月21日-22日,12月23日將舉槌拍賣。

本站聲明:網站內容來http://www.societynews.cn/html/wh/fq/,如有侵權,請聯繫我們,我們將及時處理

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

網動廣告出品的網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上她。

外觀配置虐10萬內的合資車!7.78萬起的SUV車主愛嗎?_包裝設計

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

上新台中搬家公司提供您一套專業有效率且人性化的辦公室搬遷、公司行號搬家及工廠遷廠的搬家服務

后懸挂較硬,濾振不太好。還有輪胎的胎噪比較大。車主二購買車型:優雅版 1。3T自動領尚型裸車價格:9。68萬值得表揚的地方:當初買車就是衝著帝豪GS的外觀和內飾去的,的確很漂亮,我是90后,也喜歡帝豪GS的設計。然後就是性價比了,我買的這款算中高配,配置有ESp、自動駐車、胎壓監測裝置、定速巡航、還選裝了全景天窗。

帝豪GS上市以來,市場關注度一直居高不下。它的外觀、內飾設計都展現了較高水準,可以說10萬內的合資車型在帝豪GS面前都黯然失色。下面我們就來看看第一批車主對它的評價怎樣!

吉利汽車-帝豪GS

指導價:7.78-10.88萬

基本資料

車身長寬高:4440*1833*1560mm

軸距:2700mm

動力:

1.3T+6擋雙離合/6MT

1.8L+6擋雙離合/6MT

車主一

購買車型:運動版 1.8L 手動領尚型

裸車價格:8.78萬

值得表揚的地方:

我覺得現在吉利推出的新車都很不錯,博瑞特別高端大氣、博越也很沉穩、而現在的帝豪GS和帝豪GL就很適合我們這些工薪階層。外觀是跨界風格,內飾設計精緻比較上檔次。座椅的舒適性不錯,乘坐的空間也比較大的!

1.8L發動機搭配6擋手動變速箱,動力是充足的,油門很敏感,深踩推背感還是有的!我目前行駛了600多公里了,

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

網動廣告出品的網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上她。

平均油耗大約是8.7L,我覺得這個油耗數據要繼續觀察才行,畢竟還沒過磨合期。

尚有不足:

油耗比傳祺GS4要高。后懸挂較硬,濾振不太好。還有輪胎的胎噪比較大。

車主二

購買車型:優雅版 1.3T自動領尚型

裸車價格:9.68萬

值得表揚的地方:

當初買車就是衝著帝豪GS的外觀和內飾去的,的確很漂亮,我是90后,也喜歡帝豪GS的設計。然後就是性價比了,我買的這款算中高配,配置有ESp、自動駐車、胎壓監測裝置、定速巡航、還選裝了全景天窗。配置真的不低。

動力比較充沛,而且我感覺在3000轉時動力有很大的爆發力。目前我行駛了2300公里了,平均油耗是7.6L。我覺得還不錯。

尚有不足:

導航並不好用,所以更多時候會用手機導航。胎噪太大,我覺得換一套輪胎會好一些。後備廂總體偏小,所幸後排座椅能放倒!

編輯點評:

帝豪GS較低的定價和出色的外觀內飾設計,讓很多年輕的消費者關注它,甚至選擇了它。而它的空間、動力、剎車、油耗能讓人滿意。而較大的胎噪、稍硬的后懸架等方面,也是消費者需要考慮的地方。不過帝豪GS的性價比表現真不錯!本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

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

窩窩觸角包含自媒體、自有平台及其他國家營銷業務等,多角化經營並具有國際觀的永續理念。

博物館里過元旦_包裝設計

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

網動廣告出品的網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上她。

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

窩窩觸角包含自媒體、自有平台及其他國家營銷業務等,多角化經營並具有國際觀的永續理念。

張力濤攝/光明圖片1月1日,在河北張家口地質博物館,許多市民選擇帶孩子到這裏参觀,通過觀看模型、互動體驗等形式,學習了解地質知識和生物進化進程,過了一個好玩、好看的文化節。

本站聲明:網站內容來http://www.societynews.cn/html/wh/fq/,如有侵權,請聯繫我們,我們將及時處理

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

上新台中搬家公司提供您一套專業有效率且人性化的辦公室搬遷、公司行號搬家及工廠遷廠的搬家服務

硬虐哈弗H6!油耗低/空間大/優惠大的帥氣SUV車主們愛嗎?_網頁設計

台北網頁設計公司這麼多該如何選擇?

網動是一群專業、熱情、向前行的工作團隊,我們擁有靈活的組織與溝通的能力,能傾聽客戶聲音,激發創意的火花,呈現完美的作品

車主二購買車型:2016款 2。0TGI TST四驅旗艦版裸車價:16萬元滿意的地方:外觀算是時尚吧,而且有品位,比較有個性。內飾的設計布局還不錯。乘坐空間完全夠用,後排坐上三個成年大漢完全沒有問題,後排中間的底盤幾乎是平的。

對於MG這個品牌車型,有人說不好,有人說好,且撇去外觀而言,銳騰這個車型整體來說,還是一款比較有實力的產品,但由於知名度不算高,大家對此有稍微卻步,那麼這款車到底如何,車主告訴你。

上汽集團-銳騰

指導價:10.97-17.97萬

車身尺寸

長*寬*高:4500*1855*1675mm

軸距:2650mm

動力匹配

1.5T 169馬力 L4 + 6擋手動/7擋雙離合

2.0T 220馬力 L4 + 6擋雙離合

車主一

購買車型:2016款 1.5TGI TST豪華版

裸車價:12.57萬元

滿意的地方:外觀的話,蘿蔔青菜各有所愛,不過個人是越看越喜歡,特別是那翹翹的屁股。配置比較齊全,乘坐空間很不錯,前排可以翹腿的情況小,後排也不會壓抑。轉向很精準,運動感較強,跑高速挺穩的,動力很充足,隨叫隨到,超車什麼的很輕鬆,油耗不算高,綜合油耗在8個左右,性價比較高,買的時候2萬多的優惠。

不滿意的地方:內飾的材料有些廉價,較多的硬塑料,沒有配備胎壓監測,7擋雙離合在嚴重堵車的時候有頓挫感。

車主二

購買車型:2016款 2.0TGI TST四驅旗艦版

裸車價:16萬元

滿意的地方:外觀算是時尚吧,而且有品位,比較有個性。內飾的設計布局還不錯。乘坐空間完全夠用,

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

擁有專業的維修技術團隊,同時聘請資深iphone手機維修專家,現場說明手機問題,快速修理,沒修好不收錢

後排坐上三個成年大漢完全沒有問題,後排中間的底盤幾乎是平的。轉向低速輕,高速重,很穩,路感很清晰,動力很強勁,提速很快,畢竟是8秒能破百。8.7的綜合油耗,跟同級別的車型相比看,算低了。在這個價位上的車型而言,性價比算高,優惠較大。

不滿意的地方:內飾塑料感太強,減少了一些檔次,懸挂稍微有些硬,雙離合還是有一些頓挫感,後備箱的儲存空間小了一點。

編輯總結:銳騰的售價雖然比較高一些,但它的優惠幅度也是蠻大的,最高能去到2萬多點的優惠,算上優惠的話和同級別車型對比,那麼性價比就比較高了,除了內飾的用料確實不怎麼樣之外,在配置和空間這兩大選車要素都能滿足到現在消費的需求,在操控上,檔位比較清晰,響應很快,動力對於家用車來說,是滿出來的,由於偏向運動的調校,懸挂方面稍微硬了一些。總的來說,銳騰不算優惠的話確實貴了,但它的優惠幅度算是蠻大的,也許還能更大,這就看你的口才了,這樣對比下來,銳騰的可購買性也是不低的。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

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

窩窩以「數位行銷」「品牌經營」「網站與應用程式」「印刷品設計」等四大主軸,為每一位客戶客製建立行銷脈絡及洞燭市場先機。

漢蘭達都要哭了 力帆新SUV連眾泰都嚇了一驚!_貨運

※回頭車貨運收費標準

宇安交通關係企業,自成立迄今,即秉持著「以誠待人」、「以實處事」的企業信念

4L自然吸氣發動機搭配手動變速器的動力組合,從網上渠道獲得2。4L車型的價格應該在8。68-12。98萬元,2。0T車型的價格為9。98-13。98萬元,還是很吸引人的。結語:力帆在自主品牌中不算出色,但是也算积極向上型,不過由於技術和製作工藝等限制,其產品質量和性能都很難擠進主流自主品牌當中,既然在外觀上下了那麼大工夫,何不走好質量和性能這個關卡呢,山寨之路還能走多遠,還是踏踏實實做好自己,不管怎樣,這款年底上市的力帆X80還是挺令人期待的。

力帆一家子從做摩托車辛辛苦苦發展到現在的汽車產業,也是不容易啊,大家印象最深的應該就是那款和MINI長得很像的力帆320了,這幾年都流行SUV了,二胎政策也開放了,咱們直接出一輛大7座SUV吧,於是漢蘭達躺槍了。

聽說還是力帆大公子親自參与設計的一輛車,如果拋開品牌不和豐田漢蘭達對比的話,這款X80的外觀看上去還是挺前衛時尚的,但國人的眼睛是雪亮的,這明明就是山寨了上一代漢蘭達,六邊形的進氣格柵與前大燈相互呼應,抄襲永無止境啊。

內飾從曝光圖片上看做得還算精緻吧,

※評比南投搬家公司費用收費行情懶人包大公開

搬家價格與搬家費用透明合理,不亂收費。本公司提供下列三種搬家計費方案,由資深專業組長到府估價,替客戶量身規劃選擇最經濟節省的計費方式

傳統的T形布局中控台搭配着中央显示屏,整體給你比較規整的感覺,鍍鉻飾條和仿木飾板的增加營造了不錯的豪華氛圍,但是中控台仍保留了許多實體功能按鍵,用起來會不會有着眼花繚亂的感覺呢!

車身尺寸方面根據某網站的曝光是長寬高為4820*1930*1760mm,軸距2790mm,屬於標準身材,稍微比漢蘭達短了一點點,預計將提供5門5座和7門7座的版本可供消費者選擇,所以空間上面基本是無需太多顧慮了。

力帆X80主推是2.0T渦輪增壓發動機,最大功率141千瓦,搭配着6速自動變速器,與目前市場上不同品牌同排量的車型相比,差距不是很大,此外還有個2.4L自然吸氣發動機搭配手動變速器的動力組合,從網上渠道獲得2.4L車型的價格應該在8.68-12.98萬元,2.0T車型的價格為9.98-13.98萬元,還是很吸引人的。

結語:力帆在自主品牌中不算出色,但是也算积極向上型,不過由於技術和製作工藝等限制,其產品質量和性能都很難擠進主流自主品牌當中,既然在外觀上下了那麼大工夫,何不走好質量和性能這個關卡呢,山寨之路還能走多遠,還是踏踏實實做好自己,不管怎樣,這款年底上市的力帆X80還是挺令人期待的。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

※智慧手機時代的來臨,RWD網頁設計為架站首選

網動結合了許多網際網路業界的菁英共同研發簡單易操作的架站工具,及時性的更新,為客戶創造出更多的網路商機。