標籤: 如何寫文案

  • 都在講DevOps,但你知道它的發展趨勢嗎?

    都在講DevOps,但你知道它的發展趨勢嗎?

    根據最近的一項集體研究,DevOps的市場在2017年創造了約29億美元的產值,預計到2022年,這個数字將達到約66億美元。人工智能的融入和安全性的融入,加上向自動化的巨大轉變,可合理預測,在2020年,DevOps將成為軟件工程的主流模式。

    DevOps具有以下優勢:

    ●對需求變更的迅速響應

    ●超快的交付速度及靈活的安全部署

    ●建立完善的協作溝通渠道

    ●快速識別代碼中的錯誤或漏洞

    ●讓團隊將注意力集中在其他關鍵的事情上,而不是集中在安全特性上

     

    越來越多的企業正採用DevOps的產品交付模式:根據Statista的統計數據,全面採用DevOps的企業數量從2017年的約10%增長到了2018年的17%。

    而devops也將在2020年迎來新趨勢。

     

    自動化成為焦點

    實施DevOps產品交付模式的組織已經見證了極高的效率和超快速的部署速度。在提到DevOps時,我們主要討論的是DevOps自動化,零接觸自動化是未來的發展方向。在DevOps生命周期的7C(持續發展、持續集成、持續測試、持續反饋、持續監測、持續部署、持續運維)中,應用自動化是未來的關鍵,因為預計這將是2020年的主要目標之一。

    注意力從CI管道轉移到DevOps的裝配線

    DevOps的重要目標是改進交付過程的計劃階段和自動化階段之間的協作。這不僅僅關乎CI(持續集成),更重要的是關乎CD(持續交付)。許多組織正在投入額外的精力和時間來使公司軟件開發的整個過程自動化。因此,對於這些組織來說,現在是聯繫DevOps諮詢服務提供商的時候了。預計到2020年,注意力將從CI管道轉移到DevOps的裝配線。裝配線的一些共同優點如下:

    ●原生集成

    ●堅固的嵌套可見性

    ●適當互用性的完美持續交付

    ●基於團隊的分析以及商業智能

    ●快速實現和擴展“一切皆代碼”理念

     

    對無服務器架構的使用增加

    使用無服務器架構可以將DevOps提升到更高的水平,這並不意味着沒有服務器,而是使用雲服務的整體架構。FaaS(Function as a Service,功能即服務)和BaaS(Backend as a Service,後端即服務)是無服務器架構的兩個關鍵方面。通過採用這種無服務器體繫結構,企業可以節省時間、降低成本,並擁有具有彈性的、靈活的工作流。

    “一切皆代碼”的概念

    程序編碼是IT部門及其服務系統的骨幹。對DevOps自動化工具和腳本的充分理解將支配整個2020年。這個特定IT領域的前景與產品的未來取決於開發人員、測試人員及運維人員的技術能力。現在,隨着交付周期的縮短,需要引入代碼來提高軟件生產周期的效率。“一切皆代碼”的概念是在DevOps內部完成代碼的SDLC的實踐。如果軟件測試人員還不開始學習編程和編寫測試腳本,工作很可能會受到阻礙。

    更好的嵌入式安全性

    隨着安全漏洞的出現,越來越多的大小企業意識到網絡安全的重要性。2020年,DevOps預計將迅速將安全問題納入流程。DevSecOps首先在應用程序的開發生命周期中注入安全性,這有助於減少各種缺陷和漏洞,增加業務信譽。公司轉向DevSecOps促使項目中每個人都擔負安全方面的責任,這將在軟件開發過程中帶來很棒的協作,因為它確保了軟件開發過程始終保持完美、高效和可操作。

    人工智能的興起和數據科學的飛速發展

    隨着人工智能驅動的應用程序大量增加,數據科學正在推動越來越多的公司在其工作流程中採用DevOps理念。隨着數據科學和開發團隊在軟件開發、部署以及人工智能驅動的應用程序管理方面的效率越來越高,這將會進一步推動數據科學的發展。

    2020年的主要目標是實現零接觸自動化。 持續不斷的人工智能和數據科學熱潮改變着遊戲規則。 許多應用程序都引入了人工智能,這已經促使多個DevOps團隊通過人工智能和數據科學實現自動化,數據科學團隊和開發團隊相輔相成地提高彼此的技能與交付水平。

    對無服務器架構的使用增加

    使用無服務器架構可以將DevOps提升到更高的水平,這並不意味着沒有服務器,而是使用雲服務的整體架構。FaaS(Function as a Service,功能即服務)和BaaS(Backend as a Service,後端即服務)是無服務器架構的兩個關鍵方面。通過採用這種無服務器體繫結構,企業可以節省時間、降低成本,並擁有具有彈性的、靈活的工作流。

    Kubernetes長足發展

    Kubernetes提供了基於容器技術的分佈式架構領先方案產品,因自身性能及易用性,已經成為應用廣泛的容器技術。伴隨着各類企業進一步通過深度採用容器技術來運行它們的雲原生應用,K8s將會迎來更廣的普及、更大的發展。

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

    【其他文章推薦】

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

    新北清潔公司,居家、辦公、裝潢細清專業服務

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

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

    ※超省錢租車方案

  • 使用 Masstransit中的 Request/Response 與 Courier 功能實現最終一致性

    簡介

      目前的.net 生態中,最終一致性組件的選擇一直是一個問題。本地事務表(cap)需要在每個服務的數據庫中插入消息表,而且做不了此類事務 比如:創建訂單需要 餘額滿足+庫存滿足,庫存和餘額處於兩個服務中。masstransit 是我目前主要用的方案。以往一般都用 masstransit 中的 sagas 來實現 最終一致性,但是隨着併發的增加必定會對sagas 持久化的數據庫造成很大的壓力,根據stackoverflow 中的一個回答 我發現了 一個用  Request/Response 與 Courier 功能 實現最終一致性的方案 Demo地址。

    Masstransit 中 Resquest/Response 功能 

     消息DTO

        public class SampleMessageCommand
        {
        }
    

     消費者

        public class SampleMessageCommandHandler : IConsumer<SampleMessageCommand>
        {
            public async Task Consume(ConsumeContext<SampleMessageCommand> context)
            {
                await context.RespondAsync(new SampleMessageCommandResult() { Data = "Sample" });
            }
        }
    

     返回結果DTO

     

        public class SampleMessageCommandResult
        {
            public string Data { get; set; }
        }
    

     調用方式與註冊方式略過,詳情請看 官方文檔。

      

      本質上使用消息隊列實現 Resquest/Response,客戶端(生產者)將請求消息發送至指定消息隊列並賦予RequestId和ResponseAddress(臨時隊列 rabbitmq),服務端(消費者)消費消息並把 需要返回的消息放入指定ResponseAddress,客戶端收到 Response message  通過匹配 RequestId 找到 指定Request,最後返回信息。

    Masstransit 中 Courier  功能

      通過有序組合一系列的Activity,得到一個routing slip。每個 activity(忽略 Execute Activities) 都有 Execute 和 Compensate 兩個方法。Compensate 用來執撤銷 Execute 方法產生的影響(就是回退 Execute 方法)。每個 Activity Execute 最後都會 調用 Completed 方法把 回退所需要的的信息記錄在message中,最後持久化到消息隊列的某一個消息中。

     餘額扣減的Activity ,這裏的 DeductBalanceModel 是請求扣減的數據模型,DeductBalanceLog 是回退時需要用到的信息。

    public class DeductBalanceActivity : IActivity<DeductBalanceModel, DeductBalanceLog>
        {
            private readonly ILogger<DeductBalanceActivity> logger;
            public DeductBalanceActivity(ILogger<DeductBalanceActivity> logger)
            {
                this.logger = logger;
            }
            public async Task<CompensationResult> Compensate(CompensateContext<DeductBalanceLog> context)
            {
                logger.LogInformation("還原餘額");
                var log = context.Log; //可以獲取 所有execute 完成時保存的信息
                //throw new ArgumentException("some things were wrong");
                return context.Compensated();
            }
    
            public async Task<ExecutionResult> Execute(ExecuteContext<DeductBalanceModel> context)
            {
    
                logger.LogInformation("扣減餘額");
                await Task.Delay(100);
                return context.Completed(new DeductBalanceLog() { Price = 100 });
            }
        }

     

          扣減庫存 Activity

        public class DeductStockActivity : IActivity<DeductStockModel, DeductStockLog>
        {
            private readonly ILogger<DeductStockActivity> logger;
            public DeductStockActivity(ILogger<DeductStockActivity> logger)
            {
                this.logger = logger;
            }
            public async Task<CompensationResult> Compensate(CompensateContext<DeductStockLog> context)
            {
                var log = context.Log;
                logger.LogInformation("還原庫存");
                return context.Compensated();
            }
    
            public async Task<ExecutionResult> Execute(ExecuteContext<DeductStockModel> context)
            {
                var argument = context.Arguments;
                logger.LogInformation("扣減庫存");
                await Task.Delay(100);
                return context.Completed(new DeductStockLog() { ProductId = argument.ProductId, Amount = 1 });
            }
        }
    

           生成訂單 Execute Activity

        public class CreateOrderActivity : IExecuteActivity<CreateOrderModel>
        {
            private readonly ILogger<CreateOrderActivity> logger;
            public CreateOrderActivity(ILogger<CreateOrderActivity> logger)
            {
                this.logger = logger;
            }
            public async Task<ExecutionResult> Execute(ExecuteContext<CreateOrderModel> context)
            {
                logger.LogInformation("創建訂單");
                await Task.Delay(100);
                //throw new CommonActivityExecuteFaildException("當日訂單已達到上限");
                return context.CompletedWithVariables(new CreateOrderResult { OrderId="111122",Message="創建訂單成功" });
            }
        }
    

      組裝 以上 Activity 生成一個 Routing Slip,這是一個有序的組合,扣減庫存=》扣減餘額=》生成訂單

                var builder = new RoutingSlipBuilder(NewId.NextGuid());
    builder.AddActivity("DeductStock", new Uri($"{configuration["RabbitmqConfig:HostUri"]}/DeductStock_execute"), new DeductStockModel { ProductId = request.Message.ProductId }); builder.AddActivity("DeductBalance", new Uri($"{configuration["RabbitmqConfig:HostUri"]}/DeductBalance_execute"), new DeductBalanceModel { CustomerId = request.Message.CustomerId, Price = request.Message.Price }); builder.AddActivity("CreateOrder", new Uri($"{configuration["RabbitmqConfig:HostUri"]}/CreateOrder_execute"), new CreateOrderModel { Price = request.Message.Price, CustomerId = request.Message.CustomerId, ProductId = request.Message.ProductId });
    var routingSlip = builder.Build();

      執行 Routing Slip

    await bus.Execute(routingSlip);
    

      

          這裡是沒有任何返回值的,所有activity都是 異步執行,雖然所有的activity可以執行完成或者由於某個Activity執行出錯而全部回退。(其實這裡有一種更壞的情況就是 Compensate 出錯,默認情況下 Masstransit 只會發送一個回退錯誤的消息,後面講到創建訂單的時候我會把它塞到錯誤隊列里,這樣我們可以通過修改 Compensate bug后重新導入到正常隊列來修正數據),這個功能完全滿足不了 創建訂單這個需求,執行 await bus.Execute(routingSlip) 后我們完全不知道訂單到底創建成功,還是由於庫存或餘額不足而失敗了(異步)。

         還好 routing slip 在執行過程中產生很多消息,比如 RoutingSlipCompleted ,RoutingSlipCompensationFailed ,RoutingSlipActivityCompleted,RoutingSlipActivityFaulted 等,具體文檔,我們可以訂閱這些事件,再結合Request/Response 實現 創建訂單的功能。

    實現創建訂單(庫存滿足+餘額滿足)長流程

    創建訂單 command 

        /// <summary>
        /// 長流程 分佈式事務
        /// </summary>
        public class CreateOrderCommand
        {
            public string ProductId { get; set; }
            public string CustomerId { get; set; }
            public int Price { get; set; }
        }

      事務第一步,扣減庫存相關 代碼

      public class DeductStockActivity : IActivity<DeductStockModel, DeductStockLog>
        {
            private readonly ILogger<DeductStockActivity> logger;
            public DeductStockActivity(ILogger<DeductStockActivity> logger)
            {
                this.logger = logger;
            }
            public async Task<CompensationResult> Compensate(CompensateContext<DeductStockLog> context)
            {
                var log = context.Log;
                logger.LogInformation("還原庫存");
                return context.Compensated();
            }
    
            public async Task<ExecutionResult> Execute(ExecuteContext<DeductStockModel> context)
            {
                var argument = context.Arguments;
                logger.LogInformation("扣減庫存");
                await Task.Delay(100);
                return context.Completed(new DeductStockLog() { ProductId = argument.ProductId, Amount = 1 });
            }
        }
        public class DeductStockModel
        {
            public string ProductId { get; set; }
        }
        public class DeductStockLog
        {
            public string ProductId { get; set; }
            public int Amount { get; set; }
        }

     事務第二步,扣減餘額相關代碼

    public class DeductBalanceActivity : IActivity<DeductBalanceModel, DeductBalanceLog>
        {
            private readonly ILogger<DeductBalanceActivity> logger;
            public DeductBalanceActivity(ILogger<DeductBalanceActivity> logger)
            {
                this.logger = logger;
            }
            public async Task<CompensationResult> Compensate(CompensateContext<DeductBalanceLog> context)
            {
                logger.LogInformation("還原餘額");
                var log = context.Log;
                //throw new ArgumentException("some things were wrong");
                return context.Compensated();
            }
    
            public async Task<ExecutionResult> Execute(ExecuteContext<DeductBalanceModel> context)
            {
    
                logger.LogInformation("扣減餘額");
                await Task.Delay(100);
                return context.Completed(new DeductBalanceLog() { Price = 100 });
            }
        }
        public class DeductBalanceModel
        {
            public string CustomerId { get; set; }
            public int Price { get; set; }
        }
        public class DeductBalanceLog
        {
            public int Price { get; set; }
        }

     事務第三步,創建訂單相關代碼

     public class CreateOrderActivity : IExecuteActivity<CreateOrderModel>
        {
            private readonly ILogger<CreateOrderActivity> logger;
            public CreateOrderActivity(ILogger<CreateOrderActivity> logger)
            {
                this.logger = logger;
            }
            public async Task<ExecutionResult> Execute(ExecuteContext<CreateOrderModel> context)
            {
                logger.LogInformation("創建訂單");
                await Task.Delay(100);
                //throw new CommonActivityExecuteFaildException("當日訂單已達到上限");
                return context.CompletedWithVariables(new CreateOrderResult { OrderId="111122",Message="創建訂單成功" });
            }
        }
        public class CreateOrderModel
        {
            public string ProductId { get; set; }
            public string CustomerId { get; set; }
            public int Price { get; set; }
        }
        public class CreateOrderResult
        {
            public string OrderId { get; set; }
            public string Message { get; set; }
        }

       我通過 消費 創建訂單 request,獲取 request 的 response 地址與 RequestId,這兩個值 返回 response 時需要用到,我把這些信息存到 RoutingSlip中,並且訂閱 RoutingSlipEvents.Completed | RoutingSlipEvents.Faulted | RoutingSlipEvents.CompensationFailed 三種事件,當這三種消息出現時 我會根據 事件類別 和RoutingSlip中 之前加入的 (response 地址與 RequestId)生成 Response ,整個過程大概就是這麼個意思,沒理解可以看demo。這裏由於每一個事物所需要用到的 RoutingSlip + Request/Response 步驟都類似 可以抽象一下(模板方法),把Activity 的組裝 延遲到派生類去解決,這個代理類Masstransit有 ,但是官方沒有顧及到 CompensationFailed 的情況,所以我乾脆自己再寫一個。

        public abstract class RoutingSlipDefaultRequestProxy<TRequest> :
            IConsumer<TRequest>
            where TRequest : class
        {
            public async Task Consume(ConsumeContext<TRequest> context)
            {
                var builder = new RoutingSlipBuilder(NewId.NextGuid());
    
                builder.AddSubscription(context.ReceiveContext.InputAddress, RoutingSlipEvents.Completed | RoutingSlipEvents.Faulted | RoutingSlipEvents.CompensationFailed);
                
                builder.AddVariable("RequestId", context.RequestId);
                builder.AddVariable("ResponseAddress", context.ResponseAddress);
                builder.AddVariable("FaultAddress", context.FaultAddress);
                builder.AddVariable("Request", context.Message);
    
                await BuildRoutingSlip(builder, context);
    
                var routingSlip = builder.Build();
    
                await context.Execute(routingSlip).ConfigureAwait(false);
            }
    
            protected abstract Task BuildRoutingSlip(RoutingSlipBuilder builder, ConsumeContext<TRequest> request);
        }


     這個 是派生類 Routing slip 的拼裝過程 

        public class CreateOrderRequestProxy : RoutingSlipDefaultRequestProxy<CreateOrderCommand>
    
        {
            private readonly IConfiguration configuration;
            public CreateOrderRequestProxy(IConfiguration configuration)
            {
                this.configuration = configuration;
            }
            protected override Task BuildRoutingSlip(RoutingSlipBuilder builder, ConsumeContext<CreateOrderCommand> request)
            {
                builder.AddActivity("DeductStock", new Uri($"{configuration["RabbitmqConfig:HostUri"]}/DeductStock_execute"), new DeductStockModel { ProductId = request.Message.ProductId });
    
                builder.AddActivity("DeductBalance", new Uri($"{configuration["RabbitmqConfig:HostUri"]}/DeductBalance_execute"), new DeductBalanceModel { CustomerId = request.Message.CustomerId, Price = request.Message.Price });
    
                builder.AddActivity("CreateOrder", new Uri($"{configuration["RabbitmqConfig:HostUri"]}/CreateOrder_execute"), new CreateOrderModel { Price = request.Message.Price, CustomerId = request.Message.CustomerId, ProductId = request.Message.ProductId });
    
                return Task.CompletedTask;
            }
        }

      構造response 基類,主要是對三種情況做處理。

     

        public abstract class RoutingSlipDefaultResponseProxy<TRequest, TResponse, TFaultResponse> : IConsumer<RoutingSlipCompensationFailed>, IConsumer<RoutingSlipCompleted>,
            IConsumer<RoutingSlipFaulted>
            where TRequest : class
            where TResponse : class
            where TFaultResponse : class
        {
            public async Task Consume(ConsumeContext<RoutingSlipCompleted> context)
            {
                var request = context.Message.GetVariable<TRequest>("Request");
                var requestId = context.Message.GetVariable<Guid>("RequestId");
    
                Uri responseAddress = null;
                if (context.Message.Variables.ContainsKey("ResponseAddress"))
                    responseAddress = context.Message.GetVariable<Uri>("ResponseAddress");
    
                if (responseAddress == null)
                    throw new ArgumentException($"The response address could not be found for the faulted routing slip: {context.Message.TrackingNumber}");
    
                var endpoint = await context.GetResponseEndpoint<TResponse>(responseAddress, requestId).ConfigureAwait(false);
    
                var response = await CreateResponseMessage(context, request);
    
                await endpoint.Send(response).ConfigureAwait(false);
            }
    
            public async Task Consume(ConsumeContext<RoutingSlipFaulted> context)
            {
                var request = context.Message.GetVariable<TRequest>("Request");
                var requestId = context.Message.GetVariable<Guid>("RequestId");
    
                Uri faultAddress = null;
                if (context.Message.Variables.ContainsKey("FaultAddress"))
                    faultAddress = context.Message.GetVariable<Uri>("FaultAddress");
                if (faultAddress == null && context.Message.Variables.ContainsKey("ResponseAddress"))
                    faultAddress = context.Message.GetVariable<Uri>("ResponseAddress");
    
                if (faultAddress == null)
                    throw new ArgumentException($"The fault/response address could not be found for the faulted routing slip: {context.Message.TrackingNumber}");
    
                var endpoint = await context.GetFaultEndpoint<TResponse>(faultAddress, requestId).ConfigureAwait(false);
    
                var response = await CreateFaultedResponseMessage(context, request, requestId);
    
                await endpoint.Send(response).ConfigureAwait(false);
            }
            public async Task Consume(ConsumeContext<RoutingSlipCompensationFailed> context)
            {
                var request = context.Message.GetVariable<TRequest>("Request");
                var requestId = context.Message.GetVariable<Guid>("RequestId");
    
                Uri faultAddress = null;
                if (context.Message.Variables.ContainsKey("FaultAddress"))
                    faultAddress = context.Message.GetVariable<Uri>("FaultAddress");
                if (faultAddress == null && context.Message.Variables.ContainsKey("ResponseAddress"))
                    faultAddress = context.Message.GetVariable<Uri>("ResponseAddress");
    
                if (faultAddress == null)
                    throw new ArgumentException($"The fault/response address could not be found for the faulted routing slip: {context.Message.TrackingNumber}");
    
                var endpoint = await context.GetFaultEndpoint<TResponse>(faultAddress, requestId).ConfigureAwait(false);
    
                var response = await CreateCompensationFaultedResponseMessage(context, request, requestId);
    
                await endpoint.Send(response).ConfigureAwait(false);
            }
            protected abstract Task<TResponse> CreateResponseMessage(ConsumeContext<RoutingSlipCompleted> context, TRequest request);
    
            protected abstract Task<TFaultResponse> CreateFaultedResponseMessage(ConsumeContext<RoutingSlipFaulted> context, TRequest request, Guid requestId);
            protected abstract Task<TFaultResponse> CreateCompensationFaultedResponseMessage(ConsumeContext<RoutingSlipCompensationFailed> context, TRequest request, Guid requestId);
        }

     Response 派生類 ,這裏邏輯可以隨自己定義,我也是隨便寫了個 CommonResponse和一個業務錯誤拋錯(犧牲了一點性能)。

        public class CreateOrderResponseProxy :
                RoutingSlipDefaultResponseProxy<CreateOrderCommand, CommonCommandResponse<CreateOrderResult>, CommonCommandResponse<CreateOrderResult>>
        {
    
            protected override Task<CommonCommandResponse<CreateOrderResult>> CreateResponseMessage(ConsumeContext<RoutingSlipCompleted> context, CreateOrderCommand request)
            {
    
                return Task.FromResult(new CommonCommandResponse<CreateOrderResult>
                {
                    Status = 1,
                    Result = new CreateOrderResult
                    {
                        Message = context.Message.Variables.TryGetAndReturn(nameof(CreateOrderResult.Message))?.ToString(),
                        OrderId = context.Message.Variables.TryGetAndReturn(nameof(CreateOrderResult.OrderId))?.ToString(),
                    }
                });
            }
            protected override Task<CommonCommandResponse<CreateOrderResult>> CreateFaultedResponseMessage(ConsumeContext<RoutingSlipFaulted> context, CreateOrderCommand request, Guid requestId)
            {
                var commonActivityExecuteFaildException = context.Message.ActivityExceptions.FirstOrDefault(m => m.ExceptionInfo.ExceptionType == typeof(CommonActivityExecuteFaildException).FullName);
                if (commonActivityExecuteFaildException != null)
                {
                    return Task.FromResult(new CommonCommandResponse<CreateOrderResult>
                    {
                        Status = 2,
                        Message = commonActivityExecuteFaildException.ExceptionInfo.Message
                    });
                }
                // system error  log here
                return Task.FromResult(new CommonCommandResponse<CreateOrderResult>
                {
                    Status = 3,
                    Message = "System error"
                });
            }
    
            protected override Task<CommonCommandResponse<CreateOrderResult>> CreateCompensationFaultedResponseMessage(ConsumeContext<RoutingSlipCompensationFailed> context, CreateOrderCommand request, Guid requestId)
            {
                var exception = context.Message.ExceptionInfo;
                // lg here context.Message.ExceptionInfo
                return Task.FromResult(new CommonCommandResponse<CreateOrderResult>
                {
                    Status = 3,
                    Message = "System error"
                });           
            }
        }

    對於  CompensationFailed 的處理 通過 ActivityCompensateErrorTransportFilter 實現 發送到錯誤消息隊列,後續通過prometheus + rabbitmq-exporter + alertmanager 觸發告警 通知相關人員處理。

      public class ActivityCompensateErrorTransportFilter<TActivity, TLog> : IFilter<CompensateActivityContext<TActivity, TLog>>
            where TActivity : class, ICompensateActivity<TLog>
            where TLog : class
        {
            public void Probe(ProbeContext context)
            {
                context.CreateFilterScope("moveFault");
            }
    
            public async Task Send(CompensateActivityContext<TActivity, TLog> context, IPipe<CompensateActivityContext<TActivity, TLog>> next)
            {
                try
                {
                    await next.Send(context).ConfigureAwait(false);
                }
                catch(Exception ex)
                {
                    if (!context.TryGetPayload(out IErrorTransport transport))
                        throw new TransportException(context.ReceiveContext.InputAddress, $"The {nameof(IErrorTransport)} was not available on the {nameof(ReceiveContext)}.");
                    var exceptionReceiveContext = new RescueExceptionReceiveContext(context.ReceiveContext, ex);
                    await transport.Send(exceptionReceiveContext);
                }
            }
        }

    註冊 filter 

        public class RoutingSlipCompensateErrorSpecification<TActivity, TLog> : IPipeSpecification<CompensateActivityContext<TActivity, TLog>>
            where TActivity : class, ICompensateActivity<TLog>
            where TLog : class
        {
            public void Apply(IPipeBuilder<CompensateActivityContext<TActivity, TLog>> builder)
            {
                builder.AddFilter(new ActivityCompensateErrorTransportFilter<TActivity, TLog>());
            }
    
            public IEnumerable<ValidationResult> Validate()
            {
               yield return this.Success("success");
            }
        }
    
    
                cfg.ReceiveEndpoint("DeductStock_compensate", ep =>
                {
                    ep.PrefetchCount = 100;
                    ep.CompensateActivityHost<DeductStockActivity, DeductStockLog>(context.Container, conf =>
                     {
                         conf.AddPipeSpecification(new RoutingSlipCompensateErrorSpecification<DeductStockActivity, DeductStockLog>());
                     });
    
                });

     

    實現創建產品(創建完成+添加庫存)

    實現了 創建訂單的功能,整個流程其實是同步的,我在想能不能實現最為簡單的最終一致性 比如 創建一個產品 ,然後異步生成它的庫存 ,我發現是可以的,因為我們可以監聽到每一個Execute Activity 的完成事件,並且把出錯時的信息通過 filter 塞到 錯誤隊列中。

    這裏的代碼就不貼了,詳情請看 demo

     

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

    【其他文章推薦】

    新北清潔公司,居家、辦公、裝潢細清專業服務

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

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

    ※超省錢租車方案

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

  • 一條update SQL語句是如何執行的

    一條update SQL語句是如何執行的

      一條更新語句的執行過程和查詢語句類似,更新的流程涉及兩個日誌:redo log(重做日誌)和binlog(歸檔日誌)。比如我們要將ID(主鍵)=2這一行的值加(c:字段)1,SQL語句如下:

    update T set c=c+1 where ID=2;
    • redo log

      重做日誌是InnoDB引擎特有的,是物理日誌,記錄在“某個數據頁上做了什麼修改“。大小是固定,可以進行配置大小。假如我們配置一組4個文件,圖中write pos是當前記錄的位置,往後推移並且循環;checkpoint是當前要擦除的位置,移動規律和前者一樣。兩者之間的位置可以記錄新的操作

      

      如果write pos 追上checkpoint,就移動checkpoint擦除一些記錄。所以即使數據可以發生異常重啟,InnoDB也可以保證之前提交的記錄不會丟,這就是MySQL的crash_safe能力。

    • binlog

       歸檔日誌是MySQL的server層的實現的,所有引擎都可以使用。binlog記錄的是sql語句的原始邏輯,比如根劇’id’字段查詢所有的信息;相比redo log的循環寫入,binlog是追加寫的,binlog文件寫到一定大小後會切換到下一個,不會覆蓋以前的日誌。

      Binlog有兩種模式,statement 格式的話是記sql語句, row格式會記錄行的內容,記兩條,更新前和更新后都有。

     文章開頭的更新語句在InnoDB中的執行流程如下:深色代表在執行器中執行的,淺色是在存儲引擎中。

      最後寫入redolog分為了prepare和commit兩步,用來保證兩個日誌寫入的一致性,這就是“兩階段提交”。比如我們執行“update T set status = 1“時:

    • 如果寫入redolog成功,但寫binlog失敗,重啟恢復時,數據庫發現沒有commit,那麼事務本身回滾;備份恢復時沒有binlog,數據庫里的status值不變。
    • 如果在commit失敗,重啟恢復時redolog和binlog一致,重新commit;備份恢復時有binlog,直接恢復。

      總的來說binlog記錄了對數據庫所有的邏輯操作,可以通過binlog來備份出一份完全相同的庫;因為redolog是InnoDB引擎特有的,如果使用其他引擎,那麼就要依賴binlog來記錄操作。

    Q定期全量備份的周期“取決於系統重要性,有的是一天一備,有的是一周一備”。那麼在什麼場景下,一天一備會比一周一備更有優勢呢?或者說,它影響了這個數據庫系統的哪個指標?

    A一天一備,那麼如果需要恢複數據的話,只要保證當天的binlog完整即可;一周一備的話就要保證一周的binlog完整;同時頻繁全量備份需要更多存儲空間,如何選擇取決於業務的重要性,對應的指標是RTO(目標恢復時間)。

     — 《MySQL實戰45講》筆記二

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

    【其他文章推薦】

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

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

    ※回頭車貨運收費標準

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

    ※超省錢租車方案

  • 畢業了,我的四年大學:平凡但不平庸(寫給每一位想要認真學習的小夥伴)

    畢業了,我的四年大學:平凡但不平庸(寫給每一位想要認真學習的小夥伴)

    去年十月份的時候,我分享了一篇關於我三年大學的文章:普普通通,我的三年大學,說實話,這篇文章還是激勵了不少小夥伴,給很多人帶來了動力。寫這篇那會,我剛結束了自己的秋招,結束秋招的那一刻,可以說是 2019 年心情最放鬆的時刻了,對於沒有讀研的學生來說,秋招那一戰,可以算是整個大學最重要的一戰了,在這裏,也希望各位小夥伴,也要早點做好準備。

    時間是過的真快,一不小心就在公司實習了差不多三個月了,大學最後的作業 —- 答辯和論文,也都處理完畢了,可以說我的大學,即將要畫上一個句號了。

    我的四年大學,可以說是普普通通,在校期間也沒啥輝煌的戰績,更準確的說是,0 戰績:沒有參加過什麼大賽,甚至沒有參加過比賽;在學校也沒拿過獎學金,連個三好學生都不給;也沒去用心融入一個社團,成為裏面的一員;更沒有去做過兼職,發過海報。

    但正是因為普普通通,我才要分享自己的四年大學。一個簡單的原因就是,我的公眾號里有 65% 是在校生,並且我相信大部分人和我一樣,都是普普通通,所以我希望我在大學的做法,或許可以給你們一個參考。不過說實話,雖然我的大學沒有什麼輝煌的戰績,但我還算滿意,至少是按照我自己的計劃走過來的,並且自己的目標也都實現了。

    大一

    大一第一學期這部分的事情最多了,所以會寫的多一些,因為我覺得,在大一,我的思維發生了很大的改變,大一過後,我的想法、計劃就基本有着落了,後面基本事按照自己的計劃,去做自己喜歡的事了。

    大一第一學期的第一要務:轉專業

    看過我之前文章的讀者可能都知道,我大一第一學期不是計算機專業的,讀的專業是木材科學與工程。當然,這不是我自己選擇的專業,而是高考分數不夠,被調劑的專業,我自己熱愛的專業是計算機類專業,還沒有進入大學之前,我就說我一定要讀編程相關的專業了。

    說實話,我很慶幸自己最後轉到了計算機類的專業,一個很現實的原因就是,學編程,真的能夠掙錢,而且你願意努力學的話,你一畢業就能夠拿到很高的工資,本科畢業,有好些可以拿到年薪 50 萬,你敢信?就算工資很低,也都能年薪十幾萬,當然,我不能暴露自己的工資,暴露了就要被公司勸退了。

    是的,我讀計算機專業,有兩個重要的原因,一個原因就是熱愛,一個是家裡窮,我想掙錢,改變家裡的現狀。所以在我大一第一學期那一會,我的目標就很明確,這學期的任務就是轉專業,所以我了解了轉專業的各種規則,了解到轉專業的一個最基本的要求就是數學和英語需要 85 分以上才能參与考試。

    85 分很難嗎?不難;但是如果需要你保證 85 分難嗎?說實話,有一些,因為萬一考不好呢?考不好就得大二轉了,所以我第一學期,英語的平時分,我給刷到了 100 分,也就是滿分。數學是我的強項,但我依然不敢掉以輕心,在考前把 2006-2015 年的試卷都刷了一兩遍。

    有這樣的準備,數學和英語的分數那必須杠杠,實不相瞞,大一學期的績點是我整個大學的巔峰,然而,三等獎依然沒有拿到!!!因為我只學了這兩門,其他隨意,不掛科即可。

    當然,85 分只是一個門檻,轉專業的競爭還是挺激烈。我選了軟件工程,參与人數大概是差不多 60 人,不過學院只接 15 個人(後來好像是錄取了 20 人),所以為了穩一些,我在第一學期就把 c 語言自學了一遍,把學校的 OJ 題庫,第一學期相關的編程題,給刷了一兩遍。

    說實話,有了這樣的準備,轉專業想不成功都難,考試 2 小時,我不到半個小時就離開考場了,然而我居然是第三個離開考場的,而不是第一個。第一個和第二個離開考場的,單挑嗎?

    有必要加入社團嗎?

    對於剛步入大學的同學來說,社團是一個比較有趣的玩意,絕大部分人都會去加入自己喜歡的社團吧。

    我進入大學之前,很多人說,在大學,要多交點人脈,多認識一些朋友,因為這些人會成為你後面很重要的資源,所以我去加入一些社團,其實是想多認識一些人,然而,我去面試了兩個社團,都在二面被刷了,說實話,對於面試,我還是有點恐懼的,反正就是挺緊張,後來我就乾脆不去面試了,不加入社團了。

    後來我就加入了他們的會員,之前面試那個,是成為他們的幹部。成為會員則不需要面試,不過需要交會員費用,所以我就順便成為了幾個社團的會員,例如羽毛球,愛心社團啥的。

    不過,我去參与了一兩次社團的活動之後,就沒在去了,一個簡單的原因就是,我不大喜歡,我還是喜歡去做自己喜歡的事。我是一個懶散,喜歡自由,不喜歡被束縛的人,所以我覺得,我還是不去參加這些活動了。

    回答剛才的問題,有必要加入社團嗎?,可能很多人會覺得,必須要加入社團,不然大學就不完整了,我的想法則是,加入社團不是一個必選項,加與不加,我覺得都沒事,看你自己的喜歡。加入社團能學到很多東西嗎?能學到一些,但這些沒啥的,對於以後找工作,我覺得屬於可有可無。如果你自己願意學習,學習能力比較強,在哪裡都可以學到很多東西。

    擺脫的社團之後,我基本就什麼組織也沒參加了,然後我宿舍也有一個和我一樣比較逗比+沙雕的,我倆就經常去外面溜達,看到有趣的活動,就去參与一波,反正完全看心情,報名了活動,交了錢,心情不好就不去參加了,美滋滋(感覺要被噴,哈哈)

    有必要多認識些人嗎?

    我剛才說了,我希望自己在大學多認識些人,多泛交些朋友,後來經過第一學期的感悟,我發現,這沒必要,在大學,大家基本都在忙各自的事情,我本來還想在大學找幾個摯友,以後一起干大事,但經過一學期的觀察,發現這很難,當然,很難不代表不存在。

    總之,對於現在還在大一大二的學生,如果你有這方面的疑問,那麼我給的建議是,沒必要刻意去交朋友,其實後面大家都各自去做自己的事情了,畢竟找到一些經的起時間考驗的志同道合的朋友,很不容易。

    第二學期:落差之后的折騰

    大一學期其實可以寫的還有非常非常多,因為第一學期,我的任務就兩門課程,還是非常閑,期間也發生了非常多改變我想法多事情,不過一不小心就兩千多字了,還是不繼續寫了,後面的時光可能就沒有第一學期那麼豐富了,相對比較枯燥了。

    經過了第一學期,成功進入了軟件工程,自己也沒參与什麼社團,並且大學想要干點事,例如創業之類的,因為沒讀大學之前,經常聽說創業這事……總之,進入大學的時候,感覺前途一片光明,我覺得我要干非常非常多的事,但進入大學之後,發現並沒有啥戰績。

    慶幸的事,我完成了一個非常堅定的目標,那便是轉專業。轉過來軟工之後,我也想干點大事,例如 acm 拿個牛逼的獎牌,或者寫個牛逼的軟件出來,因為經常聽到某某人開發了一個 xx,然後就成名了。

    然而,學了 c 語言,發現啥也寫不出來,學了算法,發現 acm 那些題也太難了,一道題做一天,還是沒做出來,答案也看不懂,發現自己並不是大神,腦子也並沒有大佬轉的快。

    後來,我就不打算參加 acm 了,感覺如果自己要拿到名次,肯定會花很多時間,並且不一定拿的到,加上我看到班裡也有人退出 acm 集訓隊了,這更加堅定了我的想法。

    聽說數據結構與算法很重要,所以我早早就把數據結構與算法這門課學完了,我第二學期學的最多的就是數據結構 + 算法這兩門課,雖然不參加 ACM,但算法還是得學,會點算法聽說會顯的牛逼一些。

    暑假的折騰

    大一暑假那會,雖然數據結構與算法學的還不錯,不過發現啥東西的做不出來,C 語言寫的程序都是黑乎乎的界面,然後我就學了 windows 程序編程,這樣我就可以寫個程序給身邊的人玩了,畢竟我是學編程的,至少得寫個作品出來給別人玩啊。

    在暑假花了十幾天,把那本 900 多頁的windows程序編程刷了 700 多頁,寫了個計算器,後來發現身邊的人還是玩不了,因為很多人沒電腦,於是我就對 windows 編程沒興趣了,想着寫個程序能夠在手機運行就好了,於是花了十幾天學了 android 編程,刷完了《第一行代碼》這本書,順便入門了 Java,跟着書寫了個天氣預報,還是挺開心。

    不過我又改變主意了,想着要是能掙到錢就更好了,於是我發現把 app 上傳到商店,然後植入廣告,就可以掙錢了,於是我買了一些實戰類 android 項目的書籍,寫了幾個 app,自己改版之後傳到了應用商店。

    然而,沒啥人下載,於是我又放棄搞安卓了,後來想寫個網站,然後放一些資源,讓別人來下載好像也有機會掙錢,於是我學習了 HTML,CSS,JavaScript,然後又不了了之……

    這兩個月的暑假,我感覺自己搞了好多東西,好多都是半途放棄,實不相瞞,驅動我去折騰這些事有兩個原因:掙錢 + 裝逼。說實話,我做什麼事情,都需要驅動力,我覺得驅動力對我來說太重要了,這個驅動力可以很虛,但必須得有,而我又是一個俗人,能夠掙錢,是我最大的驅動力。當初我玩斗地主,驅動力是掙 Q 幣,後來我發現這些 Q 幣好像不能充 QQ會 員還是怎麼的,我就放棄不玩了。

    這段折騰,我覺得讓我慢慢摸清了自己的方向,所以在這裏,對於大一或者大二的同學,如果你們有自己感興趣的,或許可以去嘗試一波。別人可能會說,這搞一下,那搞一下,會導致樣樣都會,但樣樣不精。而我的想法是:完全可以去嘗試,大學的學習,不存在精通這一說法,大一大二多嘗試,大三確定自己的方向來學習完全來得及。

    大二

    經過大一的洗禮,我覺得我的目標相對比較明確了,該玩的玩了,該折騰的折騰了,現在得好好規劃下自己的未來了。我了解到校招時大廠非常看重基礎,於是我大二就一直在學習基礎,例如計算機網絡,算法,操作系統這些,怎麼學?

    得有動力啊,於是我報名了中級軟考,這算是我一個動力,這門考試會考整個大學涉及到的知識,於是為了搞定這個軟考,我大二把很多課程都學了,後面軟考也順利通過了。

    中級軟考有必要參加嗎?答是隨便,這個證沒啥含金量的,我的目的是讓他督促我學習基礎知識,適合用來複習知識吧。

    總之,大一,我學了很多數據結構和算法相關知識,大二,我學了很多基礎知識 + Java 的知識,並且大二比較專心,啥比賽,啥活動也沒參加,我說了,我喜歡做自己喜歡做的事,喜歡跟着自己的步伐走,別人的建議,我可能會參考下,但我無論做什麼事,都有自己的想法和思考。

    有人也有問,有必要加入實驗室嗎?,我沒加過實驗室,但我想說的是,加與不加,都沒關係,重點是你想學習什麼,想成為什麼樣的人,實驗室,更多的是一種氣氛,但不一定適合你。要是我加入實驗室,我可能會把實驗室當作一個學習的場地,進而去學習自己喜歡的東西。

    也有人問,那些基礎知識很枯燥,有沒有什麼辦法?,答是沒有,有些本來就枯燥,但枯燥的東西,往往是決定你我之間的區別,如果都很有趣,那大家肯定也都學,正是因為困難,所以才有了人與人之間的區別。

    大三

    其實我大三基本就處於複習 + 寫作 + 運營公眾號了,關於寫作和運營公眾號這個事,我不想說太多,因為我覺得我可以再寫兩篇文章來說這些事了。我只能說,運營一個公眾號,很不容易,我希望你,好好積累,好好準備秋招或者考研。我之所以能夠在大三寫出那麼多原創文章,一個很重要的原因就是,我大一大二積累了很多,所以大三就輸入的很少,基本處於輸出和折騰公眾號的過程。

    大三的第二學期就是春招了,也就是找暑假實習,不過在大三,一般都面臨兩個選擇:讀研 還是 找工作?,實不相瞞,我從來沒想過讀研,讀研從來都沒在我的字典里。

    為什麼?

    之前也有挺多小夥伴問我為什麼沒讀研的,不過這個話題,我覺得我可以寫一篇關於我自己為何沒有讀研的原因了,如果你們感興趣,我後面寫一篇吧。

    總的來說就是,大三處於複習的過程,之前我也曬過自己的思維導圖:當初為了有機會進大廠,帥地狠心複習了這9門核心知識,熬夜整理成思維導圖送給大家

    不過說實話,其實我大三花在寫文章 + 弄公眾號的時間,佔比非常非常多,公眾號給我的學習狀態,帶來了很多負面影響,但幸運的是,我的公眾號做的不錯,給我帶來了不少收入,同時也幫忙了不少人,很多人都來感謝過我,這讓我很開心。

    大三,能說的太多,但更多的都是非技術學習,我這裏就不說了,這篇文章字數也挺多了,有機會後面再說。

    大四

    大三暑假,也就是 2019 年 9月份,我就結束了自己的秋招了,很幸運,找到了自己喜歡的公司與城市,2019 年這一年,真的可以說是非常幸運,找到了工作 + 有了自己的公眾號,所以大四,過的很輕鬆,畢竟沒有找工作的壓力,所以大四上學期,都是在 玩 + 為工作準備 + 運營公眾號

    到了大四第二學期,也就是 2020 年的 2 月份底,我就來公司實習了,一直實習到至今,關於實習到感悟,有機會再寫篇文章吧。

    總結

    說實話,我的大學,0 比賽 0 獎學金 0 證書,算是平凡但不平庸,但我始終都有一個明確目標支撐我去學習與探索,總的來說就是,我的大學做對了三件事,一是選擇了編程,二是學習正確的技能,三是入坑了寫作。

    說實話,如果你們願意學習,你們也是可以做到的,至少,你們畢業后的薪資會對的起你們平時的學習。

    我是帥地,一個即將畢業,步入社會大學的學生,希望在未來,我們共同成長,也歡迎大家見證我的成長!

    最後,獻上我備戰校招的思維導圖 + 提升內功的 PDF 吧

    九大思維導圖助你拿到心儀的 offer

    打開計算機網絡的思維導圖長這樣

    由於鏈接容易失效,不方便更新,大家可以在我的微信公眾號帥地玩編程回復思維導圖,即可獲取九大思維導圖,相信一定可以在面試時助你一臂之力。

    作者簡潔

    作者:大家好,我是帥地,從大學、自學一路走來,深知算法計算機基礎知識的重要性,所以申請了一個微星公眾號『帥地玩編程』,專業於寫這些底層知識,提升我們的內功,帥地期待你的關注,和我一起學習。 轉載說明:未獲得授權,禁止轉載

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

    【其他文章推薦】

    ※超省錢租車方案

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

    ※回頭車貨運收費標準

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

  • F-立凱公布 8 月營收 續創新高

    9 月 1 日甫與日本電池技術領導者索尼簽署電動車鋰電池合作備忘錄的 F-立凱,公布 8 月單月合併營收為 9700 萬元,年增 92.4%,月增 16.4%。繼 7 月營收增長 64.7%,8 月營收續創歷史新高。受惠於兩岸挺進新能源車推動的政策方向確定,新能源車及儲能市場需求旺盛,推動電池正極材料銷售上揚。   立凱電是國內電動巴士系統與磷酸鐵鋰電池正極材料龍頭,該公司表示,下半年客戶需求強勁,產能滿載,看好未來車用電池與儲能電池及 4G 基地台之終端應用成長,立凱電材料歷年累積銷量已達 4700 噸。

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

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

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

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

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

  • 算法崗面試題:模型的bias和variance是什麼?用隨機森林舉例

    算法崗面試題:模型的bias和variance是什麼?用隨機森林舉例

    校招在即,準備準備一些面試可能會用到的東西吧。希望這次面試不會被掛。

    基本概念

    說到機器學習模型的誤差,主要就是bias和variance。

    • Bias:如果一個模型的訓練錯誤大,然後驗證錯誤和訓練錯誤都很大,那麼這個模型就是高bias。可能是因為欠擬合,也可能是因為模型是弱分類器。

    • Variance:模型的訓練錯誤小,但是驗證錯誤遠大於訓練錯誤,那麼這個模型就是高Variance,或者說它是過擬合。

    這個圖中,左上角是低偏差低方差的,可以看到所有的預測值,都會落在靶心,完美模型;

    右上角是高偏差,可以看到,雖然整體數據預測的好像都在中心,但是波動很大。

    【高偏差vs高方差】
    在機器學習中,因為偏差和方差不能兼顧,所以我們一般會選擇高偏差、低方差的左下角的模型。穩定性是最重要的,寧可所有的樣本都80%正確率,也不要部分樣本100%、部分50%的正確率。個人感覺,穩定性是學習到東西的體現,高方差模型與隨機蒙的有什麼區別?

    隨機森林為例

    上面的可能有些抽象,這裏用RandomForest(RF)來作為例子:
    隨機森林是bagging的集成模型,這裏:
    \(RF(x)=\frac{1}{B}\sum^B_{i=1}{T_{i,z_i}(x)}\)

    • RF(x)表示隨機森林對樣本x的預測值;
    • B表示總共有B棵樹;
    • \(z_i\)表示第i棵樹所使用的訓練集,是使用bagging的方法,從所有訓練集中進行行採樣和列採樣得到的子數據集。

    這裏所有的\(z\),都是從所有數據集中隨機採樣的,所以可以理解為都是服從相同分佈的。所以不斷增加B的數量,增加隨機森林中樹的數量,是不會減小模型的偏差的。
    【個人感覺,是因為不管訓練再多的樹,其實就那麼多數據,怎麼訓練都不會減少,這一點比較好理解】

    【RF是如何降低偏差的?】
    直觀上,使用多棵樹和bagging,是可以增加模型的穩定性的。怎麼證明的?

    我們需要計算\(Var(T(x))\)
    假設不同樹的\(z_i\)之間的相關係數為\(\rho\),然後每棵樹的方差都是\(\sigma^2\).

    先複習一下兩個隨機變量相加的方差如何表示:
    \(Var(aX+bY)=a^2 Var(X)+b^2 Var(Y) + 2ab cov(X,Y)\)

    • Cov(X,Y)表示X和Y的協方差。協方差和相關係數不一樣哦,要除以X和Y的標準差:
      \(\rho=\frac{cov(X,Y)}{\sigma_X \sigma_Y}\)

    下面轉成B個相關變量的方差計算,是矩陣的形式:

    很好推導的,可以試一試。

    這樣可以看出來了,RF的樹的數量越多,RF方差的第二項會不斷減小,但是第一項不變。也就是說,第一項就是RF模型偏差的下極限了。

    【總結】

    • 增加決策樹的數量B,偏差不變;方差減小;
    • 增加決策樹深度,偏差減小;\(\rho\)減小,\(\sigma^2\)增加;
    • 增加bagging採樣比例,偏差減小;\(\rho\)增加,\(\sigma^2\)增加;

    【bagging vs boost】
    之前也提到過了boost算法:
    一文讀懂:GBDT梯度提升
    GBDT中,在某種情況下,是不斷訓練之前模型的殘差,來達到降低bias的效果。雖然也是集成模型,但是可以想到,每一個GBDT中的樹,所學習的數據的分佈都是不同的,這意味着在GBDT模型的方差會隨着決策樹的數量增多,不斷地增加。

    • bagging的目的:降低方差;
    • boost的目的:降低偏差

    喜歡的話請關注我們的微信公眾號~【你好世界煉丹師】。

    • 公眾號主要講統計學,數據科學,機器學習,深度學習,以及一些參加Kaggle競賽的經驗。
    • 公眾號內容建議作為課後的一些相關知識的補充,飯後甜點。
    • 此外,為了不過多打擾,公眾號每周推送一次,每次4~6篇精選文章。

    微信搜索公眾號:你好世界煉丹師。期待您的關注。

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

    【其他文章推薦】

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

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

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

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

    新北清潔公司,居家、辦公、裝潢細清專業服務

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

  • MapReduce 論文閱讀筆記

    MapReduce 論文閱讀筆記

    目錄

    • Abstract
    • Introduction
    • 2 Programming Model
      • 2.1 Example
      • 2.2 Types
      • 2.3 More Examples
    • 3 Implementation
      • 3.1 Execution Overview
      • 3.2 Master Data Structures
      • 3.3 Fault Tolerance
        • Worker Failure (工作節點故障)
        • Master Failure(主節點故障)
        • Semantics in the Presence of Failures(語義可能存在的故障)
      • 3.4 Locality
      • 3.5 Task Granularity
      • 3.6 Backup Tasks
    • 4 Refinements
      • 4.1 Partitioning Function
      • 4.2 Ordering Guarantees
      • 4.3 Combiner Function
      • 4.4 Input and Output Types
      • 4.5 Side-effects
      • 4.6 Skipping Bad Records
      • 4.7 Local Execution
      • 4.8 Status Information
      • 4.9 Counters
    • Others

    Abstract

    MapReduce :

    • programming model 編程模型
    • an associated implementation for processing and generating large data sets.

    用戶只需要指定 Map(Map函數將 key/value 類型的 pair 生成中間結果的 pair) 和 Reduce 函數(Reduce 函數將所有具有相同中間結果的值組合起來)即可。

    MapReduce 封裝隱藏了分佈式系統并行計算的細節:

    • 輸入數據的分割
    • 計劃將程序分配到一組計算機中
    • 處理機器故障
    • 管理集群內部的通信

    程序(in functional style)分佈式的運行在大型分佈式的集群上,而且具有很好的可伸縮性 scalable。

    Introduction

    過去這些年,Google一直在尋找方法來實現處理大量數據(抓取到的文件,web日誌等)的方法,通常數據量很大而且必須分散在數以千計的電腦上來進行運算。為了處理如何使計算相互關聯,分配數據以及處理故障的問題,往往編寫大量的複雜代碼掩蓋了他們,最初的簡單計算的初衷卻被忽略掉。

    為了解決這種複雜性,抽象出了一個簡單的計算模型放到一個庫中,這個庫隱藏了可能出現的問題:

    • 并行計算
    • 容錯
    • 數據分發
    • 負載均衡

    這個抽象受到了 Lisp 以及很多函數式編程語言中存在的原語 mapreduce 的啟發。

    大多數并行計算都包含兩個步驟:

    • map:將每個邏輯記錄變成 key/value 的中間形式方便計算
    • reduce:將所有具有相同 key 的值組合到一起來進行合適的處理

    我們使用一個函數式的編程模型(functional programming model)可以讓處理大型的并行計算和使用重新執行作為容錯的主要機制變得很簡單。

    這項工作的主要貢獻是:提供了一個簡單但是很強大的接口(interface)讓自動化的并行計算和大規模計算的分發成為可能,結合該接口的實現,可以在商用機的大型集群上實現高性能。

    Section2 :描述了基本的編程模型給出幾個例子

    Section3 :描述MapReduce 接口針對集群運算環境的實現

    Section4 :一些針對該模型的細微的改良

    Section5 :針對實現設計出一系列性能衡量方法

    Section6 :MapReduce 在 Google 中的使用,以及使用 MapReduce 來重寫生產環境的索引系統

    Section7 :相關以及未來的工作

    2 Programming Model

    input: a set of key/value pairs

    output: a set of key/value pairs

    MapReduce 的用戶將只會使用兩個函數 MapReduce

    Map:用戶編寫,將輸入的 pair 變成 k/v 的中間 pairs,然後 MapReduce 會把具有相同 key 的 pair 送給 Reduce 函數

    Reduce: 用戶編寫,接受中間結果 key 和 key 的一系列值。將這些值組合起來成為更少的 k/v;通常每個 Reduce 函數只輸出一個 或者 0 個值。中間結果太多無法全部放到內存中,可以通過迭代的方法來處理大量的 value

    2.1 Example

    設想一個需要統計文件中每個單詞數量的一個問題,我們很可能編寫這樣的代碼:

    map(String key, String value):
    	// key: document name
    	// value: document contents
    	for each word w in value:
    		EmitIntermediate(w, "1")
          
    reduce(String key, String values):
    	// key: a word
    	// values: a list of counts
    	int result = 0
      for each v in values
        result += ParseInt(v)
      Emit(AsString(result))
    

    map:給每個單詞添加一個屬性(出現的次數,這裏就是1)

    reduce:給每個特定的單詞加起來計算總數並且提交

    此外,用戶編寫代碼以使用輸入和輸出文件的名稱以及可選的調整參數來填充mapreduce規範對象。然後,用戶調用MapReduce函數,並將其傳遞給指定對象。用戶代碼與MapReduce庫(在C ++中實現)鏈接在一起。

    2.2 Types

    儘管前面偽代碼使用 string 來寫的輸入輸出,但是從概念上說,是由用戶來指定 map 和 reduce 的類型

    map (k1, v1)  ->  list(k2, v2)
    map (k2, list(v2)) -> list(v2)
    

    輸入值和中間值來自不同的域,中間值和輸出值來自相同的域

    C ++實現在用戶定義的函數之間來回傳遞字符串,並將其留給用戶代碼以在字符串和適當的類型之間進行轉換

    2.3 More Examples

    這有一些可以使用 MapReduce 簡化的計算:

    Distributed Grep: 分佈式的匹配,map 函數提交一個符合匹配的 line, reduce 的作用只是複製中間結果到輸出

    Count of URL Access Frequency: 網頁訪問計數,map 處理網頁請求並且輸出中間結果為 <URL, 1>, reduce 功能是將所有的相同的 URL 計算到一起提交為 <URL, total count>

    Reverse Web-Link Graph: 翻轉網絡鏈接圖, map 輸出 <target, source> pairs ,將 target 命名為 source。reduce函數連接與給定目標URL關聯的所有源URL的列表,並提交該對 <target, list(source)>

    Term-Vector per Host: 術語向量是出現在一篇文章中最重要的術語集合列表<word, frequency> pairs。map 函數給每個輸入文件輸出一個 <hostname, term vector> pairs,reduce 函數傳遞給特定主機的術語向量,然後去掉不常出現的向量最後提交一個 <hostname, term vector> pair

    Invert Index:map函數解析每個文檔,併發出一系列<單詞,文檔ID>對。 reduce函數接受給定單詞的所有對,對相應的文檔ID進行排序,併發出一個“單詞,列表(文檔ID)”對。setofall輸出對形成一個簡單的倒排索引。易於擴展此計算以跟蹤單詞位置。

    Distributed Sort: 分佈式排序,map函數功能從每個記錄中提取鍵,併發出一個<key, record>對。 reduce函數將所有對保持不變。這種計算取決於第4.1節中描述的分區功能和第4.2節中描述的排序屬性。

    3 Implementation

    MapReduce 可以有很多不同的實現,正確實現是根據你自己所在的環境來進行實現,例如某個實現可能很適合一個共享內存的小機器,某個實現可能是在NUMA多處理器的環境下,也可能是在一個大的網絡連接的集群的機器中。

    NUMA Non-uniform memory access

    非統一內存訪問架構是一種為多處理器的電腦設計的內存架構,內存訪問時間取決於內存相對於處理器的位置。在NUMA下,處理器訪問它自己的本地內存的速度比非本地內存快一些。 非統一內存訪問架構的特點是:被共享的內存物理上是分佈式的,所有這些內存的集合就是全局地址空間

    這節描述的是 Google 雲計算環境下廣泛使用的,下面是 Google 的配置:

    1. 機器,都是典型的Linux系統,運行在基於x86的雙處理器上,每台機器 2-4GB 內存
    2. 網絡,使用商品網絡硬件,在機器級別通常為100Mb/s或1Gb/s,但平均平均對分帶寬要小得多
    3. 集群中有上百或者上千個機器,所以機器故障出現很正常
    4. 存儲,使用廉價的 IDE 硬盤直接保存每個機器自己的數據,開發出的分佈式文件系統來管理這些磁盤上的文件。文件系統使用複製來在不可靠的硬件上提供可用性和可靠性。
    5. 用戶通過一個任務調度系統提交任務。每個工作包含一系列的任務,使用任務調度器來分配到集群中可用的機器上

    3.1 Execution Overview

    Map 調用分佈在多個機器上,自動將輸入數據分配成 M 組,輸入的分割可以并行的發生在不同的機器上。 Reduce 調用也是分佈式的,通過將中間值的key使用一個分割函數(例如:hash(key) mod R)來將任務分配到不同的機器上。分區數量 R 的取值和分區數量也是通過用戶來指定的。

    下面這個圖說明了 MapReduce 執行的完整流程:

    1. 用戶程序中的 MapReduce 庫首先將輸入文件分成 M 份(每份 16MB – 64 MB),然後開始在集群中複製很多拷貝
    2. 程序中有一份拷貝是特殊的(master)。剩下的 worker 來被分配工作,有 M 個map任務和 R 個 reduce 任務來分配給不同的 worker。master 來挑選空閑的 worker 分配給他們每個一個 map task 或者 reduce task
    3. 一個被分配到 map 工作的 worker 將會從對應的分割的內容中讀取。它會解析 k/v pair 到輸入數據並且傳給到用戶定義的 map 函數。Map 函數產生的中間 key/value 數據將會被保存在內存的緩存中
    4. 緩存中的 pair 會被周期性的寫入到本地磁盤上,通並且過分割函數將該文件分成的 R 個區域。這些緩衝對的位置在本地磁盤上被傳遞迴主服務器,該主服務器負責將這些位置轉發給reduce worker。
    5. 當一個 reduce worker 被 master 告知這些存儲的區域,reduce worker 將使用遠程過程調用來從 map workers 的本地磁盤以及緩存中讀取 pair 對。當一個 reduce worker 讀取了所有的中間數據,它將會跟配相同的 key 來進行排序。排序是必需的因為通常會有很多不同的 key 映射到同一個 reduce 任務。如果中間數據太大來放到內存中排序,外排序就會被使用
    6. reduce worker 迭代排過序的中間數據,對於每個獨特的中間值 key,它會傳遞這個 key 和對應中間值到用戶定義的 reduce 函數中來處理。Reduce函數的輸出將附加到此reduce分區的最終輸出文件中
    7. 當所有的 map task 和 reduce task 都被完成之後,master 喚醒用戶程序。這個時候,MapReduce 的調用返回到用戶的代碼邏輯中

    在成功的完成之後,mapreduce執行的輸出將會在 R 個輸出文件中(每個reduce task 都會被用戶指定文件的名稱)。通常來說,用戶不會將 R 個輸出文件合併成一個,而是將這個文件作為另一個 MapReduce 的輸入。或者把他們當成另外的分佈式程序的輸入

    3.2 Master Data Structures

    master 保存一些數據結構。對於每個 map 和 reduce 任務,它會保存狀態(idle,in-progress,completed),以及識別每個 worker machine(非空閑任務)。

    master 是處於 map 任務發送到 reduce 任務中間的導管,master 會保存中間文件區域的位置。因此,對於每個完成的 map 任務, master 保存 R 個由map任務產生的中間文件的大小和位置。當 map 任務完成之後,會更新這些文件的位置和大小。這些信息將逐漸被推送到已經在工作的 reduce 任務。

    3.3 Fault Tolerance

    由於 MapReduce 是用來在大量機器上處理大量數據的一個庫,所以這個庫必需能夠有很好的容錯能力。

    Worker Failure (工作節點故障)

    master 節點周期性的 ping 每個 worker 節點。如果在一個特定的時間內沒有收到回復,那麼 master 節點就會將這個 worker 標記為失敗。完成 map 任務的 worker 節點將會被重置為 idle 空閑狀態,然後就可以被其他 worker 節點安排。相似的,如果一個節點上的 map 或者 reduce 任務在執行過程中失敗了,那麼這個任務將會被重置然後分配然後重新分配。

    如果一個 map 任務的節點在完成任務之後出現故障,那麼就需要重新執行這個任務,因為這個節點變得不可訪問。但是如果是 reduce 任務完成之後節點出現故障,不需要重新執行,這是因為 reduce 任務的輸出被保存到一個全局文件系統中。

    當一個 map 任務首先在節點 A 上執行之後在節點 B 上執行時,所有正在執行的 reduce 節點將會被告知這次重新執行過程。所有還沒有從 worker A 讀取數據的 reduce 任務將會 worker B 讀取。

    MapReduce 可以處理大規模的 worker 節點故障。例如在一次 MapReduce 任務中,一個由80台計算機組成的集群由於網絡問題無法訪問,MapReduce 的 master 節點只是簡單的讓那些不能正常執行任務的工作節點再次執行任務,然後繼續向前執行任務直到完成 MapReduce 操作。

    Master Failure(主節點故障)

    讓 master 節點周期性的上述master節點的數據結構的檢查點。如果 master task 失敗了,可以從上一個檢查點的拷貝恢復。但是如果只有一個主節點,那麼出現故障的可能性非常小,因此如果主節點出現故障,我們的當前的實現就中止了此次 MapReduce 任務。客戶端可以檢查到這種情況,然後可以選擇是否重試 MapReduce 操作。

    Semantics in the Presence of Failures(語義可能存在的故障)

    當用戶指定的 map 和 reduce 操作對於他們的輸入輸出都確定好了之後,分佈式的實現將會產生一個類似於線性執行過程任務執行的結果。

    我們依賴於 map 和 reduce 任務的 原子性 commit 作為這個特性的保證。每個執行過程中的任務都會將把他的輸出保存到一個私有的臨時文件中。一個 reduce 任務產生一個這樣的文件,但是一個 map 任務將會產生 R 個這樣的文件。當一個 map 任務完成之後, worker 節點發送包含這 R 個文件名的消息到 master 節點。如果這個節點已經接收到完成的消息,那麼將會忽略這個消息,否則將會把這些文件名保存到 master 節點的數據結構中。

    當一個 reduce 任務完成的時候,reduce worker 將會原子性的將它的臨時文件重命名成一個輸出文件。如果有以個 reduce 任務在多個機器上同時完成,那麼這個重命名的操作將會對於一個輸出文件多次執行。我們依賴於基礎文件系統提供的原子重命名操作,以確保最終文件系統狀態僅包含一次執行reduce任務所產生的數據。

    map 和 reduce 操作絕大部分都是確定性的,事實上我們的語義將會和線性順序執行的程序的結果一致,這樣很容易分析程序的行為。當 map/reduce 操作是不確定的時候,我們提供弱化但是可信的語義。例如在一個不確定的語義中,一個特定 reduce 任務的輸出和這個任務順序執行的結果一致。然而,用於不同reduce任務R2的輸出可以對應於由不確定性程序的不同順序執行所產生的用於R2的輸出。(這裏保留疑問,沒有太懂什麼意思,指的是有可能是線性結果一致的意思嗎?)

    考慮有一個 map 任務 M,和兩個 reduce 任務 R1,R2,e(Ri)是 Ri提交的結果,弱一點的語義指的是,e(R1) 可能讀取的 M 的一個執行的結果而 e(R2) 可能讀取的是 R 執行輸出的另外一個結果。

    3.4 Locality

    網絡帶寬是在雲計算環境中比較稀缺的資源(盡量少用)。通過將輸入文件(由GFS保管)保存到本地磁盤上來減少網絡帶寬的使用。GFS 將每個文件分成 64 MB的塊,然後將每塊保存幾個副本(通常為3份)在不同的機器上。MapReduce 盡量將這些位置信息保存下來然後盡量將含有某個文件主機的任務分配給它,這樣就可以減少網絡的傳遞使用。如果失敗,那麼將會嘗試從靠近輸入數據的一個副本主機去啟動這個任務。當在一個集群上執行大型的 MapReduce 操作的時候,輸入數據一般都是本地讀取,減少網絡帶寬的使用。

    3.5 Task Granularity

    我們將一個 map 任務分成 M 塊,然後 reduce 會處理最後輸出成 R 塊。

    理想情況下,M 和 R 應該遠遠大於集群中的 worker 節點數量。讓每個節點執行不同的任務將會有利於動態的負載均衡,同時會加速當一個 worker 節點故障之後的恢復,map 任務完成之後可以分配到所有的其他節點上。

    R 和 M 在實現過程中會有邊界,因為 schedule 決策需要 O(M + R)的時間,保存這個信息到內存中需要 O(M*R)的複雜度(常數內存很小)。

    R 通常是由用戶指定的,這是由於每個 reduce 任務的輸出將會輸出到單獨的文件中。在實際中,通常 M 被選擇到每個單獨的任務輸入數據將會是 16MB ~ 64MB,讓 R 要比我們使用的機器的數目的小几倍。

    例如,M =20,000,R = 5,000,worker machines = 2000

    3.6 Backup Tasks

    讓整個 MapReduce 任務時間延長的原因主要有 “拖延者”:某一個機器在 map/reduce 任務上花費了太多的時間。導致這個 “拖延者” 的原因可能有很多,比如一塊讀寫速度超級慢的硬盤 1MB/s(其他的是 30MB/s)比如集群在這個機器上也分配了其他任務,這些任務競爭使用 CPU、硬盤網絡等等。我們最近遇到的一個問題是機器初始化代碼中的一個錯誤,該錯誤導致禁用了處理器緩存:受影響機器的計算速度降低了一百倍。

    4 Refinements

    儘管上面說的夠用了,但是我們還是找到一些可以優化的點。

    4.1 Partitioning Function

    用戶將會指定輸出文件的數量 R。輸入數據將會根據中間值來把這些數據分區。一個默認的分區函數就是 hash函數(hash(key) mod R) 。通常情況下這樣很好,但是在某些情況下不是很好。例如,輸出數據是 URL key,我們希望具有相同主機 hostname 的 URL 在一起,這樣,MapReduce 提供了用戶自己制定 分區函數的方式,例如可以寫為(hash(Hostname(urlkey))),這樣具有相同的 hostname 的URL將會在一個相同的輸出文件中。

    4.2 Ordering Guarantees

    我們保證了在某個給定的分區中,中間值的 k/v pair 將會按照增序排列,這樣在某些需要有序的場景下是很有用的。

    4.3 Combiner Function

    在某些情況下,最終的輸出文件reduce是需要根據中間值來合併的。例如在2.1的 word count 中,需要統計每個單詞的數目,我們輸出的是 <word, 1> 這樣的形式。這些pair都需要通過網絡來進行發送到 reduce 工作節點,我們提供了一個 combiner 函數,讓用戶可以在發送數據之前執行的函數,也就是說在本地先合併,然後再發送到網絡中去。

    在每個執行 map task 的機器都會執行 conbine 函數。

    combine 和 reduce 的唯一區別:

    • combine 輸出是到中間值文件中
    • reduce 輸出到一個最終的輸出文件中

    4.4 Input and Output Types

    MapReduce 庫提供了幾種輸入的類型。

    例如,在 “text” 模式下輸入將每一個行當作 k/v pair,這行的偏移量當作 key,這行的內容當作 value。

    用戶可以通過實現 reader 接口在實現自己的輸入類型,儘管大多數用戶都只使用預定義的類型。

    reader 不一定要從文件中讀取數據,也可以從數據庫中讀取數據,或者從內存中的某個的某個數據結構中獲取數據。

    同樣的,輸出也可以自定義。

    4.5 Side-effects

    在某些情況下,用戶需要產生一些額外的輸出文件在reduce 的輸出結果中。我們依靠這個應用程序自己的編寫者來使此類副作用成為原子和冪等的。通常應用程序將會寫入一個臨時文件中,然後當它完成的時候將會原子性的重命名這個文件。

    我們不提供原子性的兩節點提交由一個任務產生的多個輸出文件。因此,產生多個輸出的結果的任務應該是確定性的。

    4.6 Skipping Bad Records

    在處理大量數據的時候,由於用戶的 map/reduce 函數的錯誤在處理某些數據的時候產生bug,這個時候可以選擇跳過這些 bug。有的時候我們可以查找到bug所在的地方,但是有的時候我們找不到bug,因為可能是第三方的庫導致的錯誤,我們提供了一種可選的執行模式來跳過這些可能出現錯誤的記錄。

    每個 worker 進程都會有一個監聽段錯誤和總線錯誤的處理器。在執行用戶的 map/reduce 函數之前,mapreduce 將會在一個全局變量中保存順序編號。如果用戶代碼產生了一個 signal,那麼就會發送一個 UDP包到 MapReduce 的 master 節點上。當 master 節點發現在某個幾點上出現了很多次故障的時候,之後就會跳過這個記錄。

    4.7 Local Execution

    在 Map/Reduce 中debug需要一些 trick,因為在分佈式系統中執行可能是在幾千台機器中,工作分配也是動態的。MapReduce 提供了一個另外的本地MapReduce 的本地實現(順序執行),這樣就可以在本地來進行 debug了。

    4.8 Status Information

    master 節點通過 HTTP 服務器提供一個显示當前狀態的網頁。這個界面显示了多少任務完成了,多少任務還在執行,中間數據有多少字節等,還包含了錯誤的標準輸出文件的鏈接等,用戶可以通過這個界面預估還有多久可以完成任務。當執行很慢的時候,可以通過這個界面來查找原因。

    另外,top-level 的狀態信息還會显示哪些 worker 節點有故障,哪些任務失敗了。

    4.9 Counters

    提供一個全局的計數器來統計某些數據,例如統計大寫單詞的出現次數。

     Counter* uppercase;
      uppercase = GetCounter("uppercase");
      map(String name, String contents):
        for each word w in contents:
    			if (IsCapitalized(w)): 
    				uppercase->Increment(); 
    			EmitIntermediate(w, "1");
    

    計數器的值會從每個節點周期性的發送到master節點,主節點統計計數器的值並且在 狀態頁面显示。

    有些值MapReduce 會自己去統計。

    計數器功能對於完整性檢查MapReduce操作的行為很有用。例如,在某些MapReduce操作中,用戶代碼可能想要確保所生成的輸出對的數量完全等於所處理的輸入對的數量,或者所處理的德語文檔的比例在該比例之內。

    Others

    shuffle:將所有具有相同 key 的value 發送個單個的 reduce 進程,在網絡上傳輸數據,是MapReduce代價最大的部分

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

    【其他文章推薦】

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

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

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

    ※幫你省時又省力,新北清潔一流服務好口碑

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

  • 使用json-server與Mockjs搭建模擬服務

    使用json-server與Mockjs搭建模擬服務

    為什麼使用

    在項目開發中,常常需要邊寫前端頁面邊寫後端接口,但是後端接口服務往往是滯後於前端開發的,或者是不能及時提供的。出於前端開發的迅速和便捷去考慮,我們可以根據後端接口數據結構去模擬(mock)數據從而實現前端的獨立開發。
    JsonServer 主要的作用就是搭建本地的數據接口,創建json文件,便於調試調用
    Mockjs 主要的作用就是生成隨機數據,支持生成隨機的文本、数字、布爾值、日期、郵箱、鏈接、圖片、顏色等

    如何使用

    以下操作需要node.js環境

    1.創建文件夾

    選擇自己喜歡的位置創建一個文件夾,比如E:/mock

    2.安裝json-server

    進入E:/mock

    npm install json-server -g 

    安裝完執行 json-server -h,若安裝成功則會显示選項

    Options:
    –config, -c Path to config file [default: “json-server.json”]
    –port, -p Set port [default: 3000]
    –host, -H Set host [default: “localhost”]
    –watch, -w Watch file(s) [boolean]
    –routes, -r Path to routes file
    –middlewares, -m Paths to middleware files [array]
    –static, -s Set static files directory
    –read-only, –ro Allow only GET requests [boolean]
    –no-cors, –nc Disable Cross-Origin Resource Sharing [boolean]
    –no-gzip, –ng Disable GZIP Content-Encoding [boolean]
    –snapshots, -S Set snapshots directory [default: “.”]
    –delay, -d Add delay to responses (ms)
    –id, -i Set database id property (e.g. _id) [default: “id”]
    –foreignKeySuffix, –fks Set foreign key suffix (e.g. _id as in post_id)
    [default: “Id”]
    –quiet, -q Suppress log messages from output [boolean]
    –help, -h Show help [boolean]
    –version, -v Show version number [boolean]

    3.使用json-server
    • 創建json文件,如db.json,文件內容如下
    { "posts": [ { "id": 1, "title": "json-server", "author": "typicode" } ], "comments": [ { "id": 1, "body": "some comment", "postId": 1 } ], "profile": { "name": "typicode" } } 
    • 啟動json-server,cmd執行
    json-server --watch db.json 

    看到如下內容

     

    1574216715(1).jpg


    則運行成功,直接訪問圖中显示的三個地址可得結果,如


     

    1574216903(1).jpg


    至此,簡單的json-server服務已經搭建成功了,後續如果有更多需求,如跨域、參數查詢、路由這些,請參考json-server的github

     

    4.安裝Mockjs

    進入E:/mock

    npm install mockjs --save 
    5.使用Mockjs
    • 創建js文件,如news.js,文件內容如下
    let Mock=require('mockjs'); let Random=Mock.Random; module.exports=()=>{ let data={ news:[] }; let images=[1,2,3].map(x=>Random.image('120x60',Random.color(),Random.word(2,6))); for(let i=1;i<=100;i++){ let content=Random.cparagraph(0,10); data.news.push({ id:i, title:Random.cword(8,20), desc:content.substr(0,40), tag:Random.cword(2,6), views:Random.integer(100,5000), images:images.slice(0,Random.integer(1,3)) }) } return data } 
    • 啟動json-server,cmd執行
    json-server --watch news.js 

    訪問結果(部分)


     
     

    完成了以上的搭建與驗證過程后,你就可以開始使用json-server與Mockjs來繼續構建模擬服務器了,來滿足自己的各種需要

    Tips:

    1、也可以使用json-server db.json ,使用“json-server –watch db.json”命令可以實時監測db.json的變化;如果沒有 — watch 命令,即使db.json已經發生了改變,重新發請求,仍然會返回原先的mock data,返回狀態碼304,認為沒有變化。

    2、同時,我們可以發送 POST、PUT、PATCH和DELETE請求,相應的結果會通過lowdb自動保存到db.json。關於POST、PUT等相關請求的發送。

    3、我們的request body應該是一個json對象,比如{“name”:”Lynn”};

    4、POST、PUT、PATCH請求頭中要包含Content-Type: application/json;

    5、id的值是自動生成且不易變的。PUT請求和PATCH請求中自帶的id會被忽略。

     

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

    【其他文章推薦】

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

    新北清潔公司,居家、辦公、裝潢細清專業服務

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

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

    ※超省錢租車方案

  • springboot_自動配置原理

    springboot_自動配置原理

    目錄

    • 1.1 @SpringBootApplication
    • 2.1 @EnableAutoConfiguration
      • 2.1.1 @AutoConfigurationPackage
      • 2.1.2 @Import({Registrar.class})
    • 3.1 以HttpEncodingAutoConfiguration為例

    springboot啥都不難,總所周知spring全家桶系列難就難在理解源碼。。。。。。。

    今天結合網上資料,自己總結了一下springboot的自動配置原理。

    我現在使用的springboot版本為2.3.1.不同版本的springboot在源碼上有差別!但大體一致。

    管他三七二十一先打個斷點再說:

    1.1 @SpringBootApplication

    這個註解點進去我們可以看到:

    這裏面主要關注兩個東西:

    • @SpringBootConfiguration
    • @EnableAutoConfiguration
      第一個註解點進去:

      可以看到這個@SpringBootConfiguration本質就是一個@Configuration,標註在某個類上,表示這是一個Spring Boot的配置類。
      第二個註解@EnableAutoConfiguration: 開啟自動配置類,SpringBoot的精華所在。(最重要的就是這個註解)

    2.1 @EnableAutoConfiguration

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @AutoConfigurationPackage
    @Import({AutoConfigurationImportSelector.class})
    public @interface EnableAutoConfiguration {
        String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    
        Class<?>[] exclude() default {};
    
        String[] excludeName() default {};
    }
    

    兩個比較重要的註解:

    • @AutoConfigurationPackage:自動配置包。
    • @Import({AutoConfigurationImportSelector.class}):導入自動配置的組件。

    2.1.1 @AutoConfigurationPackage

    點進去瞅瞅:

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @Import({Registrar.class})
    public @interface AutoConfigurationPackage {
        String[] basePackages() default {};
    
        Class<?>[] basePackageClasses() default {};
    }
    

    發現這裡有導入Regitstrar類:

    static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
            Registrar() {
            }
    
            public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
                AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
            }
    
            public Set<Object> determineImports(AnnotationMetadata metadata) {
                return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));
            }
        }
    

    new PackageImport(metadata).getPackageName(),它其實返回了當前主程序類的 **同級以及子級 ** 的包組件。

    什麼意思呢?

    我們來看這樣一個目錄:

    bean1和我們的springboot啟動類位於同一個包下,二bean2不是位於我們啟動類的同級目錄或者子級目錄,那麼我們啟動的時候bean2是不會被加載到的!所以你項目的一切需要加入容器的類必須放在啟動類的同級包下或者它的子級目錄中。

    2.1.2 @Import({Registrar.class})

    AutoConfigurationImportSelector有一個方法為:selectImports。

    public String[] selectImports(AnnotationMetadata annotationMetadata) {
            if (!this.isEnabled(annotationMetadata)) {
                return NO_IMPORTS;
            } else {
                AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
                return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
            }
        }
    

    它首先回去檢查是否開啟了自動配置類,然後才回去加載註解數據 this.getAutoConfigurationEntry(annotationMetadata);
    那麼這個annotationMetadata在哪兒?
    來看下面一行代碼:

      protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
            List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
            Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
            return configurations;
        }
    

    SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
    再點進去我們會發現這一行代碼:
    Enumeration urls = classLoader != null ? classLoader.getResources(“META-INF/spring.factories”) : ClassLoader.getSystemResources(“META-INF/spring.factories”);

    它其實是去加載 public static final String FACTORIES_RESOURCE_LOCATION = “META-INF/spring.factories”;外部文件。這個外部文件,有很多自動配置的類。如下:

    spring.factories文件由一組一組的key=value的形式,其中一個key是EnableAutoConfiguration類的全類名,而它的value是一個xxxxAutoConfiguration的類名的列表,這些類名以逗號分隔。
    springboot項目啟動時,@SpringBootApplication用在啟動類在SpringApplication.run(…)的內部就會執行selectImports()方法,找到所有JavaConfig自動配置類的全限定名對應的class,然後將所有自動配置類加載到Spring容器中。

    3.1 以HttpEncodingAutoConfiguration為例

    @Configuration(
        proxyBeanMethods = false
    )    //表示是一個配置類,可以給容器中添加組件
    @EnableConfigurationProperties({ServerProperties.class})// 啟用ConfigurationProperties功能
    @ConditionalOnWebApplication(
        type = Type.SERVLET
    )
    @ConditionalOnClass({CharacterEncodingFilter.class})
    @ConditionalOnProperty(
        prefix = "server.servlet.encoding",
        value = {"enabled"},
        matchIfMissing = true
    )
    

    @EnableConfigurationProperties({ServerProperties.class})// 啟用ConfigurationProperties功能
    ServerProperties.class:

    @ConfigurationProperties(
        prefix = "server",
        ignoreUnknownFields = true
    )
    

    @ConditionalOnWebApplication :spring底層@Conditional註解,根據不同的條件進行判斷,如果滿足條件整個配置類才會生效。

    總結:
    1.springboot會自動加載大量的自動配置類。
    2.只要我們要用的組件有,我們就不需要再去配置
    3.給容器添加組件的時候。會從properties類中獲取某些屬性。我們就可以在配置文件中指定這些屬性。
    xxxxxAutoConfiguration:自動配置類

    給容器中添加屬性:
    xxxxProperties:封裝配置文件中的相關屬性。

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

    【其他文章推薦】

    新北清潔公司,居家、辦公、裝潢細清專業服務

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

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

    ※超省錢租車方案

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

  • Java 多線程基礎(十一)線程優先級和守護線程

     Java 多線程基礎(十一)線程優先級和守護線程

    一、線程優先級

    Java 提供了一個線程調度器來監控程序啟動後進去就緒狀態的所有線程。線程調度器通過線程的優先級來決定調度哪些線程執行。一般來說,Java的線程調度器採用時間片輪轉算法使多個線程輪轉獲得CPU的時間片。然而根據實際情況,每個線程的重要程序也不相同,有時候我們想讓一些線程優先執行,那麼我們可以將他的優先級調高一下,這樣它們獲得的時間片會多一些。

    多個線程處於就緒狀態時,若這些線程的優先級相同,則線程調度器會按時間片輪轉方式或獨佔方式來分配線程的執行時間。

    java 中的線程優先級的範圍是1~10,默認的優先級是5。“高優先級線程”會優先於“低優先級線程”執行。

    Java中線程優先級分為三個級別:

    • 低優先級:1~4,其中類變量 Thread.MIN_PRORITY 最低,數值為1;
    • 默認優先級:如果一個線程沒有指定優先級,默認優先級為5,由類變量 Thread.NORM_PRORITY表示;
    • 高優先級:6~10,類變量 Thread.MAX_PRORITY 最高,數值為10。

    注意:具有相同優先級的多個線程,若它們都為高優先級Thread.MAX_PRORITY,則每個線程都是獨佔式的,也就是這些線程將被順序執行;若它們優先級不是高優先級,則這些線程將被同時執行,可以說是無序執行。

    java 中有兩種線程:用戶線程和守護線程。可以通過 isDaemon() 方法來區別它們:如果返回 false,則說明該線程是“用戶線程”;否則就是“守護線程”。
    用戶線程一般用戶執行用戶級任務,而守護線程也就是“後台線程”,一般用來執行後台任務。需要注意的是:Java虛擬機在“用戶線程”都結束後會後退出。

    JDK中對用戶線程與守護線程的解釋:

    每個線程都有一個優先級。“高優先級線程”會優先於“低優先級線程”執行。每個線程都可以被標記為一個守護進程或非守護進程。在一些運行的主線程中創建新的子線程時,
    子線程的優先級被設置為等於“創建它的主線程的優先級”,當且僅當“創建它的主線程是守護線程”時“子線程才會是守護線程”。
    當Java虛擬機啟動時,通常有一個單一的非守護線程(該線程通過是通過main()方法啟動)。JVM會一直運行直到下面的任意一個條件發生,JVM就會終止運行:
    ①、調用了exit()方法,並且exit()有權限被正常執行。 ②、所有的“非守護線程”都死了(即JVM中僅僅只有“守護線程”)。
    每一個線程都被標記為“守護線程”或“用戶線程”。當只有守護線程運行時,JVM會自動退出。
     

    二、線程優先級示例

    public class Demo01 {
        private static Object obj = new Object();
        public static void main(String[] args) {
            
            Thread t1 = new ThreadA("t1");
            Thread t2 = new ThreadA("t2");
            
            t1.setPriority(1); // 設置優先級為1
            t2.setPriority(10);// 設置優先級為10
            
            t1.start();
            t2.start();
        }
    }
    class ThreadA extends Thread{
        public ThreadA(String name) {
            super(name);
        }
        public void run() {
            for(int i = 0;i < 3;i++)
                System.out.println(Thread.currentThread().getName() + " [ " 
                        + Thread.currentThread().getPriority() + " ] loop " + i);  
        }
    }
    // 運行結果
    t1 [ 1 ] loop 0
    t2 [ 10 ] loop 0
    t2 [ 10 ] loop 1
    t2 [ 10 ] loop 2
    t1 [ 1 ] loop 1
    t1 [ 1 ] loop 2

    說明:

    ①、主線程main的優先級是5。
    ②、t1的優先級被設為1,而t2的優先級被設為10。cpu在執行t1和t2的時候,根據時間片輪循調度,所以能夠併發執行。

    三、守護線程示例

    public class Demo {
        public static void main(String[] args) {
    
            System.out.println(Thread.currentThread().getName()
                    +" [ isDaemon = "+Thread.currentThread().isDaemon()+ " ]");
            Thread t1=new ThreadA("t1");    
            Thread t2=new MyDaemon("t2");    
            t2.setDaemon(true);// 設置t2為守護線程
            t1.start();                        
            t2.start();                        
        }
    }
    class ThreadA extends Thread{
        public ThreadA(String name) {
            super(name);
        }
    
        public void run(){
            try {
                for (int i=0; i<5; i++) {
                    Thread.sleep(3);
                    System.out.println(this.getName() +"[ isDaemon = "+this.isDaemon()+ " ] " + "loop " + i);
                }
            } catch (InterruptedException e) {
            }
        }
    };
    
    class MyDaemon extends Thread{
        public MyDaemon(String name) {
            super(name);
        }
    
        public void run(){
            try {
                for (int i=0; i<10000; i++) {
                    Thread.sleep(1);
                    System.out.println(this.getName() +"[ isDaemon = " + this.isDaemon() +  " ] " +"loop "+i);
                }
            } catch (InterruptedException e) {
            }
        }
    }
    // 運行結果
    main [ isDaemon = false ]
    t2[ isDaemon = true ] loop 0
    t2[ isDaemon = true ] loop 1
    t2[ isDaemon = true ] loop 2
    t1[ isDaemon = false ] loop 0
    t2[ isDaemon = true ] loop 3
    t2[ isDaemon = true ] loop 4
    t1[ isDaemon = false ] loop 1
    t2[ isDaemon = true ] loop 5
    t2[ isDaemon = true ] loop 6
    t2[ isDaemon = true ] loop 7
    t1[ isDaemon = false ] loop 2
    t2[ isDaemon = true ] loop 8
    t2[ isDaemon = true ] loop 9
    t2[ isDaemon = true ] loop 10
    t1[ isDaemon = false ] loop 3
    t2[ isDaemon = true ] loop 11
    t2[ isDaemon = true ] loop 12
    t2[ isDaemon = true ] loop 13
    t2[ isDaemon = true ] loop 14
    t1[ isDaemon = false ] loop 4
    t2[ isDaemon = true ] loop 15

    說明:

    ①、主線程main是用戶線程,它創建的子線程t1也是用戶線程。
    ②、t2 是守護線程。在“主線程main”和“子線程t1”(它們都是用戶線程)執行完畢,只剩t2這個守護線程的時候,JVM自動退出。

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

    【其他文章推薦】

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

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

    ※回頭車貨運收費標準

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

    ※超省錢租車方案