• 限速设计及实现(微服务架构)


    在这里插入图片描述

    开过车或者坐过车的都对限速是深恶痛绝吧,不过为了大家安全,忍一忍吧。系统究竟是如何判断车主是否超速那,本文主要介绍这个系统到底是如何设计的。本文基于微服务进行设计。

    关于微服务,在微服务初探(0)微服务-node.js中对微服务有简单的介绍。通过这篇文章,可以进一步了解微服务。

    注:本文内容参考《Dapr For .NET developer》,专有名词保留英文原文,不做翻译。

    Dapr

    The Distributed Application Runtime (Dapr) provides APIs that simplify microservice connectivity. Whether your communication pattern is service to service invocation or pub/sub messaging, Dapr helps you write resilient and secured microservices.

    Building blocks抽象了分布式应用能力的实现,service只需要和Building blocks进行交互即可。
    在这里插入图片描述

    Dapr的现在和安装可参考Install the Dapr CLI

    限速设计

    在这里插入图片描述
    这里面fine是罚单,罚款的意思。

    VehicleState包括了车牌号,进入限速区域(Cameral)时间,退出限速区域(Cameral)时间。

    public record struct VehicleState
    {
        public string LicenseNumber { get; init; }
        public DateTime EntryTimestamp { get; init; }
        public DateTime? ExitTimestamp { get; init; }
    
        public VehicleState(string licenseNumber, DateTime entryTimestamp, DateTime? exitTimestamp = null)
        {
            this.LicenseNumber = licenseNumber;
            this.EntryTimestamp = entryTimestamp;
            this.ExitTimestamp = exitTimestamp;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    据此就可以计算出平均时速,然后再与规定的时速进行比较,如果超过了,不好意思,您将收到一封邮件,也可以用短信等方式发送给车主。而车主的邮件信息通过VehicleRegistrationService来获取。

    车辆信息的结构体定义如下:

    public record struct VehicleInfo(string VehicleId, string Brand, string Model, string OwnerName, string OwnerEmail);
    
    • 1

    代码实现

    windows平台下需要将start-maildev.ps1修改为,修改参考maildev/maildev

    docker run -d -p 4000:1080 -p 4025:1025 --name dtc-maildev maildev/maildev
    
    • 1

    -d: 后台运行容器,并返回容器ID;

    (1) CameralSimulation.cs

    模拟进入和退出代码:

    // simulate entry
    DateTime entryTimestamp = DateTime.Now;
    var vehicleRegistered = new VehicleRegistered
    {
        Lane = _camNumber,
        LicenseNumber = GenerateRandomLicenseNumber(),
        Timestamp = entryTimestamp
    };
    await _trafficControlService.SendVehicleEntryAsync(vehicleRegistered);
    Console.WriteLine($"Simulated ENTRY of vehicle with license-number {vehicleRegistered.LicenseNumber} in lane {vehicleRegistered.Lane}");
    
    // simulate exit
    TimeSpan exitDelay = TimeSpan.FromSeconds(_rnd.Next(_minExitDelayInS, _maxExitDelayInS) + _rnd.NextDouble());
    Task.Delay(exitDelay).Wait();
    vehicleRegistered.Timestamp = DateTime.Now;
    vehicleRegistered.Lane = _rnd.Next(1, 4);
    await _trafficControlService.SendVehicleExitAsync(vehicleRegistered);
    Console.WriteLine($"Simulated EXIT of vehicle with license-number {vehicleRegistered.LicenseNumber} in lane {vehicleRegistered.Lane}");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    跟踪 _trafficControlService.SendVehicleEntryAsync

     public async Task SendVehicleEntryAsync(VehicleRegistered vehicleRegistered)
     {
         var eventJson = JsonSerializer.Serialize(vehicleRegistered);
         var message = new MqttApplicationMessageBuilder()
             .WithTopic("trafficcontrol/entrycam")
             .WithPayload(Encoding.UTF8.GetBytes(eventJson))
             .WithAtMostOnceQoS()
             .Build();
         await _client.PublishAsync(message, CancellationToken.None);
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    使用Input Binding来发布消息。可追踪到消息的订阅者是trafficcontrolservice。
    (2)TrafficController.cs

    [HttpPost("entrycam")]
     public async Task<ActionResult> VehicleEntryAsync(VehicleRegistered msg)
     {
         try
         {
             // log entry
             _logger.LogInformation($"ENTRY detected in lane {msg.Lane} at {msg.Timestamp.ToString("hh:mm:ss")} " +
                 $"of vehicle with license-number {msg.LicenseNumber}.");
    
             // store vehicle state
             var vehicleState = new VehicleState(msg.LicenseNumber, msg.Timestamp, null);
             await _vehicleStateRepository.SaveVehicleStateAsync(vehicleState);
    
             return Ok();
         }
         catch (Exception ex)
         {
             _logger.LogError(ex, "Error occurred while processing ENTRY");
             return StatusCode(500);
         }
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    跟踪_vehicleStateRepository.SaveVehicleStateAsync(vehicleState)

    public async Task SaveVehicleStateAsync(VehicleState vehicleState)
    {
        await _daprClient.SaveStateAsync<VehicleState>(
            DAPR_STORE_NAME, vehicleState.LicenseNumber, vehicleState);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    使用state management building block来保存VehicleState。

    同理可知退出时会调用trafficcontrolservice的VehicleExitAsync:

     [HttpPost("exitcam")]
        public async Task<ActionResult> VehicleExitAsync(VehicleRegistered msg, [FromServices] DaprClient daprClient)
        {
            try
            {
                // get vehicle state
                var state = await _vehicleStateRepository.GetVehicleStateAsync(msg.LicenseNumber);
                if (state == default(VehicleState))
                {
                    return NotFound();
                }
    
                // log exit
                _logger.LogInformation($"EXIT detected in lane {msg.Lane} at {msg.Timestamp.ToString("hh:mm:ss")} " +
                    $"of vehicle with license-number {msg.LicenseNumber}.");
    
                // update state
                var exitState = state.Value with { ExitTimestamp = msg.Timestamp };
                await _vehicleStateRepository.SaveVehicleStateAsync(exitState);
    
                // handle possible speeding violation
                int violation = _speedingViolationCalculator.DetermineSpeedingViolationInKmh(exitState.EntryTimestamp, exitState.ExitTimestamp.Value);
                if (violation > 0)
                {
                    _logger.LogInformation($"Speeding violation detected ({violation} KMh) of vehicle" +
                        $"with license-number {state.Value.LicenseNumber}.");
    
                    var speedingViolation = new SpeedingViolation
                    {
                        VehicleId = msg.LicenseNumber,
                        RoadId = _roadId,
                        ViolationInKmh = violation,
                        Timestamp = msg.Timestamp
                    };
    
                    // publish speedingviolation (Dapr publish / subscribe)
                    await daprClient.PublishEventAsync("pubsub", "speedingviolations", speedingViolation);
                }
    
                return Ok();
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error occurred while processing EXIT");
                return StatusCode(500);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    如果存在超速的话会调用:

    await daprClient.PublishEventAsync("pubsub", "speedingviolations", speedingViolation)
    
    • 1

    根据配置文件

    apiVersion: dapr.io/v1alpha1
    kind: Component
    metadata:
      name: pubsub
      namespace: dapr-trafficcontrol
    spec:
      type: pubsub.rabbitmq
      version: v1
      metadata:
      - name: host
        value: "amqp://localhost:5672"
      - name: durable
        value: "false"
      - name: deletedWhenUnused
        value: "false"
      - name: autoAck
        value: "false"
      - name: reconnectWait
        value: "0"
      - name: concurrency
        value: parallel
    scopes:
      - trafficcontrolservice
      - finecollectionservice
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    可追踪到消息的订阅者是finecollectionservice。
    (3)FineCollectionService

        [Topic("pubsub", "speedingviolations")]
        [Route("collectfine")]
        [HttpPost()]
        public async Task<ActionResult> CollectFine(SpeedingViolation speedingViolation, [FromServices] DaprClient daprClient)
        {
            decimal fine = _fineCalculator.CalculateFine(_fineCalculatorLicenseKey!, speedingViolation.ViolationInKmh);
    
            // get owner info (Dapr service invocation)
            var vehicleInfo = _vehicleRegistrationService.GetVehicleInfo(speedingViolation.VehicleId).Result;
    
            // log fine
            string fineString = fine == 0 ? "tbd by the prosecutor" : $"{fine} Euro";
            _logger.LogInformation($"Sent speeding ticket to {vehicleInfo.OwnerName}. " +
                $"Road: {speedingViolation.RoadId}, Licensenumber: {speedingViolation.VehicleId}, " +
                $"Vehicle: {vehicleInfo.Brand} {vehicleInfo.Model}, " +
                $"Violation: {speedingViolation.ViolationInKmh} Km/h, Fine: {fineString}, " +
                $"On: {speedingViolation.Timestamp.ToString("dd-MM-yyyy")} " +
                $"at {speedingViolation.Timestamp.ToString("hh:mm:ss")}.");
    
            // send fine by email (Dapr output binding)
            var body = EmailUtils.CreateEmailBody(speedingViolation, vehicleInfo, fineString);
            var metadata = new Dictionary<string, string>
            {
                ["emailFrom"] = "noreply@cfca.gov",
                ["emailTo"] = vehicleInfo.OwnerEmail,
                ["subject"] = $"Speeding violation on the {speedingViolation.RoadId}"
            };
            await daprClient.InvokeBindingAsync("sendmail", "create", body, metadata);
    
            return Ok();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    其中vehicleInfo 的值是通过service invocation调用VehicleRegistrationService获取的。

    最后通过Output Binding来发送邮件:

    await daprClient.InvokeBindingAsync("sendmail", "create", body, metadata);
    
    • 1

    运行

    1. start-all.ps1
    2. dapr run --app-id vehicleregistrationservice --app-port 6002 --dapr-http-port 3602 --dapr-grpc-port 60002 --config …/dapr/config/config.yaml --components-path …/dapr/components dotnet run
    3. dapr run --app-id finecollectionservice --app-port 6001 --dapr-http-port 3601 --dapr-grpc-port 60001 --config …/dapr/config/config.yaml --components-path …/dapr/components dotnet run
    4. dapr run --app-id trafficcontrolservice --app-port 6000 --dapr-http-port 3600 --dapr-grpc-port 60000 --config …/dapr/config/config.yaml --components-path …/dapr/components dotnet run
    5. cd Simulation && dotnet run

    最终结果:
    在这里插入图片描述

    车辆的进入退出信息都会实时捕获到,如果有超速信息,会通过邮件发送。
    在这里插入图片描述

    写在最后

    可视化版本
    cd .\VisualSimulation\ && dotnet run
    
    • 1

    在这里插入图片描述

    视频可参见: https://live.csdn.net/v/238245

    rabbitmq管理界面

    默认账号密码都是guest。
    在这里插入图片描述

    公众号

    更多内容,欢迎关注我的微信公众号: 半夏之夜的无情剑客。
    在这里插入图片描述

  • 相关阅读:
    Monkey测试
    R语言手动绘制分类Logistic回归模型的校准曲线(Calibration curve)(3)
    24.【特殊函数篇==(十篇一章,持续更新~)】
    掌握Linux技能:关键命令与测试题解析
    记一次使用vue连接rabbitMq
    初识CAN(Controller Area Network)协议
    2016年亚太杯APMCM数学建模大赛A题基于光学信息数据的温度及关键元素含量预测求解全过程文档及程序
    iNavFlight之MSP DJI协议天空端请求报文
    7.features特征
    Android studio 常用控件以布局属性
  • 原文地址:https://blog.csdn.net/helloworlddm/article/details/126752282