棒棒最棒

小朋友真可爱


  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

流量调度和编排系统

wwf 发表于 2021-06-15 | 分类于 架构

服务化管理

服务协议

  1. HTTP/1.1
  2. HTTP/2
  3. HTTP/3(QUIC)
  4. MQTT

服务路由规则

  • IP 过滤
  • URL 地址路径匹配
  • HTTP 请求头部信息匹配
  • HTTP 请求动作过滤

服务发现

  • ​ 内置Etcd

容错设计

  • 限流
  • 熔断
  • 重试
  • 超时
  • 服务降级

发布管理

  • 蓝绿发布(流量切换)
  • 灰度发布(流量调度)

API管理

  • 后端多API聚合调用
  • 后端API流程编排
  • Swagger/SDK/文档(需要后端支持Swagger)

安全相关

  • 用户 HTTPS 证书和私钥管理
  • IP地址黑白名单
  • API签名验证(HMAC)
  • JWT Token 验证
  • OAuth2 支持
  • Let’s Encrpt证书自动化管理

插件化管理

  • 新增、导入、编辑、查看、删除过滤插件
  • 插件组合 Pipeline 编排,进行流程控制
  • 用户可以定制插件,进行二次开发

Service Mesh

  • Mesh Sidecar Ingress/Egress
  • Mesh Service Registry(兼容 Eureka/Consul/Nacos)
  • Mesh 限流/熔断/重试/超时

FaaS

  • FaaS Function 以SideCar形式部署用户业务逻辑的docker 镜像, 自动伸缩Function实例个数。
  • FaaS Function 事件订阅(队列,定时任务)
  • FaaS Function 状态监控,资源消耗,延迟,请求量统计

第三方集成

  • Kubernetes Ingress Controller 集成
  • Knative Serverless 集成
  • Kafka 输出
  • 服务发现:Eureka, Consul, Etcd, Nacos

高性能可用

高并发 流量处理

  • 秒杀 - 选择服务,秒杀活动号,秒杀活动场次号
  • 过滤 - 在设计用户定制服务时,可以定制过滤规则
  • 缓存 - 为每个服务针对性定制缓存数量

负载均衡

  • 轮流分配
  • 随机分配
  • 按权随机分配
  • IP 地址哈希
  • HTTP 头部信息哈希

集群管理

  • 集群配置(配置名称,IP地址)
  • 集群自动化选主
  • 多集群配置,一键切换集群

热更新

  • 程序不停机热更新

运维

CLI

  • 命令行管理工具,提供配置更改、状态查询、排障支持等功能

管理API

  • 对外提供RESTful API,可以进行二次开发,与用户其它系统进行集成,或者进行自动化管理和运维

调用链跟踪

  • OpenTracing
  • OpenZipkin

全局监控

  • 节点名
  • 状态(健康,故障)
  • 节点角色信息 - Writer(Leader/Follower), Reader
  • IP 地址
  • 心跳时间

节点统计

  • 流量吞吐量 (TPS/m1, m5, m15)
  • 响应时间 (p50,p95,p99)
  • 请求/响应数据量 (字节数)

服务监控

  • 服务器吞吐量 (Server TPS/m1, m5, m15)
  • 后端吞吐量 (Backend TPS)
  • 服务器响应时间(p50,p95,p99)
  • 后端响应时间(p50,p95,p99)
  • 对服务按照各种指标进行排序
  • 服务器请求/响应数据量 (字节数)
  • 状态码分布

管理

配置管理

  • 集群管理
  • 服务及路由管理
  • 插件管理
  • Pipeline 管理
  • Controller 管理(服务发现、监控系统、FaaS等)

用户管理

发布管理

  • 蓝绿发布设定
  • 灰度发布设定
  • 变更预览与发布
  • 发布历史查询
  • 同步远程配置

可视化监控

  • 接口维度吞吐量展示
  • 接口维度响应时间分布式展示
  • 接口维度的吞吐, 耗时,错误率等TopN排名展示
  • 接口维度的状态码分布
  • 接口维度请求/响应数据量统计
  • Backend维度吞吐量展示
  • Backend维度响应时间分布展示
  • Backend维度的状态码分布
  • 集群节点维度吞吐量展示
  • 集群节点维度响应时间分布展示
  • 集群节点维度平均请求/响应数据量分布展示

日志保全

对网关用户和服务的所有操作均保留日志,方便安全审计,证据留存,和历史记录备案查找

美大夫预约系统

wwf 发表于 2021-06-03 | 分类于 后端

一、原始版

1、医生精准预约

后台设置医生在不同机构的每天出诊时间,出诊类型分 循环出诊 和临时出诊;

  • 设置循环出诊,生成未来90天的循环出诊信息;

​ 比如后台设置循环出诊 每周一上午10:00-12:00,会提前生成未来90天每周一10:00-12:00的数据。

  • 设置临时出诊,只是生成固定日期的出诊信息

    比如 设置 2021年7月1号-2021年7月3号 10:00-12:00 出诊,只是会生成7.1号-7.3号的出诊数据

2、实现思想

  • 后台生成医生在不同机构的出诊时间模板

  • 依赖时间模板定时生成未来90天预约数据

  • 前端根据生成的预约数据展示给用户可约数据

3、数据约束

  • 医生
  • 日期+时间
  • 机构
  • 预约类型,临时出诊,循环出诊
  • 时间宽度单位 每30分钟 为一个时间单位

4、模板生成预约数据技术实现

工厂模式 + 枚举算法

5、设计图

后台工厂模式生成预约数据设计图

6、ER图

image.png

7、后台设置

8、前端预约

image-20210602120249657

image-20210602120238712

9、代码端

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
<?php

namespace App\Helper;

use App\Models\Diagnose;
use App\Models\DiagnoseLog;
use App\Models\DiagnoseSchedule;
use App\Models\ScheduleModel;
use App\Models\Space;
use App\Models\StopDiagnose;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;

/**
* @desc 精准预约生成日期的工具类
*
* 1:添加预约模版
*
* 2:批量生成循环的3个月的预约数据
*
* 3:定时任务每天生成第90天的数据
*
* 4:修改临时出诊数据
*
* 5:设置停诊数据
*
* 6:新增出诊数据
*
* 7:添加日志
*
* 8:删除预约模版
*
* 生成每天8:00到21:30的预约
**/
class AppointmentTool
{
public $startTime = 8;
public $endTime = 22;

public $operateId='';
public $operateType='';
public $logType = '';
public $dayCount = 90;
public function __construct($operateId,$operateType,$logType)
{
$this->operateId = $operateId;
$this->operateType = $operateType;
$this->logType = $logType;
}
public function writeLog( $content,$logType ='')
{
$param['operater_id'] = $this->operateId;
$param['operater_type'] = $this->operateType;
$param['operate_content'] = $content;
$param['log_type'] = empty($logType) ? $this->logType : $logType;
DiagnoseLog::create($param);
}

/**
* 1:添加预约模版
* (1)判断是否已经有预约的模版
* (2)添加模版
*(3)生成90天的数据
***/
public function setSchduleModel($spaceId, $workUnitId, $workUnName, $daySchdules)
{
$params['space_id'] = $spaceId;
$params['work_unit_id'] = $workUnitId;
$params['work_unit_name'] = $workUnName;
foreach ($daySchdules as $daySchdule)
{
$params['day_schdule'] = '';
$flag = explode(':',$daySchdule['time']);
if((int)$flag[0]<12 && (int)$flag[0]>=8)
{
$params['day_schdule'] = 'morrning';
}
elseif((int)$flag[0]>=12 && (int)$flag[0]<18)
{
$params['day_schdule'] = 'afternoon';
}
elseif((int)$flag[0]>=18 && (int)$flag[0]<22)
{
$params['day_schdule'] = 'evening';
}
else
{
continue;
}
$params['week'] = $daySchdule['week'];
$params['time'] = $daySchdule['time'];
$params['is_stop_diagnose'] = 0;
$params['is_delete'] = 0;
$this->createSchduleModel($params);
}
return true;
}

//创建预约模版
public function createSchduleModel($param,$logType='')
{
$where['space_id'] = $param['space_id'];
$where['work_unit_id'] = $param['work_unit_id'];
$where['week'] = $param['week'];
$where['time'] = $param['time'];
$where['is_delete'] = $param['is_delete'];
$res = ScheduleModel::firstOrCreate($where,$param);
$this->writeLog('生成模版数据:'.json_encode($param),$logType);
}

/**
* 定时任务生成生成第90天的数据
**/
public function creatSchduleData()
{
$nowObj = Carbon::now();
// $nintySchdule = $nowObj->addDay($this->dayCount);
//$week = $nintySchdule->dayOfWeek;
$week =$nowObj->dayOfWeek;
$spaceIds = ScheduleModel::where('week',$week)->select('space_id')->distinct()->get();
if($spaceIds->isEmpty())
{
Log::error(date('Y-m-d H:i:s').'模版第90天数据不存在,spaceId',$spaceIds->toArray());
return false;
}
$spaceArrs = $spaceIds->toArray();
foreach ($spaceArrs as $spaceArr)
{
$spaceObj = Space::find($spaceArr['space_id']);

$modelDatas = ScheduleModel::where('week',$week)
->where('space_id',$spaceArr['space_id'])
->where('is_stop_diagnose',ScheduleModel::STOPDIAGNOSENO)
->where('is_delete',ScheduleModel::DELETENO)
->get();
if($modelDatas->isEmpty())
{
continue;
}
foreach ($modelDatas as $modelData)
{
$param['space_id'] = $modelData->space_id;
$param['work_unit_id'] = $modelData->work_unit_id;
$param['appoint_date'] = '';
$param['user_id']=$spaceObj->user_id;
$param['work_unit_name']=$modelData->work_unit_name;
$param['day_schdule'] = $modelData->day_schdule;
$param['time'] = $modelData->time;
$param['week'] = $modelData->week;
$param['is_stop_diagnose'] =$modelData->is_stop_diagnose;
$param['is_delete'] =$modelData->is_delete;
$param['number'] =1;
$param['schdule_type'] = DiagnoseSchedule::CYCLE;
$param['model_id']=$modelData->id;
$param['date_time'] = '';
$res = $this->createOperateData($param);
if($res)
{
Log::info('定时生成每周数据',$param);
}
else
{
Log::error('定时生成数据失败',$param);
}
}
}
}

/**
* @desc 4:修改或者添加临时出诊数据
**/
public function setInterimSchdule($spaceId, $userId, $workUnitId, $workUnName, $startTime, $endTime ,$schduleType,$modelId=0)
{
$timeRes = $this->changeTimeToSchdule($startTime, $endTime);
if ($timeRes['errorCode'] != 0) {
return $timeRes;
}
$timeDatas = $timeRes['data'];
foreach ($timeDatas as $timeData) {
$flag = [
'space_id' => $spaceId,
'work_unit_id' => $workUnitId,
'appoint_date' => $timeData['date'],
'user_id'=>$userId,
'work_unit_name'=>$workUnName
];
foreach ($timeData['daySchdule'] as $daySchdules)
{
$flag['day_schdule'] = $daySchdules[3];
$flag['time'] = $daySchdules[1];
$flag['week'] = $timeData['week'];
$flag['appoint_date'] =$timeData['date'];
$flag['is_stop_diagnose'] =0;
$flag['is_delete'] =0;
$flag['number'] =1;
$flag['schdule_type']=$schduleType;
$flag['model_id']=$modelId;
$flag['date_time'] = $timeData['date'].' '.$daySchdules[1];
$params = $flag;
$params['date_time'] = $timeData['date'].' '.$daySchdules[2];
$this->addScheduleData($flag);
if( isset($daySchdules[2]) && !empty($daySchdules[2]))
{
$params['time'] = $daySchdules[2];
$this->addScheduleData($params);
}
}
}
}

/**
* @desc 添加数据生成预约数据
***/
private function addScheduleData($param,$logType='')
{
$where['space_id'] = $param['space_id'];
$where['work_unit_id'] = $param['work_unit_id'];
$where['appoint_date'] = $param['appoint_date'];
$where['time'] = $param['time'];
$where['is_delete'] = $param['is_delete'];
$diagneseSchedulObjs = DiagnoseSchedule::where('space_id',$where['space_id'])
->where('work_unit_id',$where['work_unit_id'])
->where('appoint_date',$where['appoint_date'])
->where('time',$where['time'])
->where('is_delete',$where['is_delete'])
->get();
if($diagneseSchedulObjs->isEmpty())
{
$stopDiagnoseObj = StopDiagnose::where(
['space_id'=>$param['space_id'],'time'=>$param['time'],'appoint_date'=>$param['appoint_date']]
)->get();
if($stopDiagnoseObj->isNotEmpty())
{
$this->writeLog('存在停诊数据生成预约数据失败:spaceId'.$param['space_id'].'时间'.$param['time'].'日期'.$param['appoint_date']);
}
else
{
return DiagnoseSchedule::create($param);
}

}
else
{
$diagneseSchedulObj = $diagneseSchedulObjs->first();
$diagneseSchedulObj->schdule_type = $param['schdule_type'];
$diagneseSchedulObj->save();
}
$this->writeLog('生成预约数据:'.json_encode($param),$logType);
return true;
}

/***
* @desc 根据选择时间段来生成
* 1,解析时间段生成上午下午的模版
* 2,解析时间段生成每天的模版
* 3,解析时间段,生成每周的模版
*
**/
public function changeTimeToSchdule($startTime, $endTime)
{
//开始时间大于当前时间
$startTimeObj = Carbon::parse($startTime);
$endTimeObj = Carbon::parse($endTime);
$int = (new Carbon)->diffInHours($endTimeObj, false);
$nowObj = Carbon::now();
if ($int < 0) {
$message = '结束时间:' . $endTime . '小于当前时间:' . $nowObj;
Log::info($message);
return array('errorCode' => 5000, 'errorMessage' => $message, 'data' => '');
}
//结束时间小于开始时间
if (false == $endTimeObj->gt($startTimeObj)) {
$message = '结束时间' . $endTime . '小于开始时间' . $startTime;
Log::info($message);
return array('errorCode' => 5001, 'errorMessage' => $message, 'data' => '');
}

//开始时间和结束时间是同一天
if ($endTimeObj->toDateString() == $startTimeObj->toDateString()) {
return array('errorCode' => 0, 'errorMessage' => 'success', 'data' => array($this->caculateSameDay($startTimeObj, $endTimeObj)));
} else {
return array('errorCode' => 0, 'errorMessage' => 'success', 'data' => $this->caculateDiffDay($startTimeObj, $endTimeObj));
}
}

//计算不在同一天的数据
private function caculateDiffDay($startTimeObj, $endTimeObj)
{
$dayInt = $startTimeObj->diffInDays($endTimeObj) + 1;
$returnData = array();
for ($i = 1; $i <= $dayInt; ++$i) {
$startTimeParam = $startTimeObj;
$endTimeparam = $endTimeObj;
if ($i == 1) {
$flagStart = $startTimeParam->toDateString();
$endTimeparam = Carbon::parse($flagStart . ' 22:00:00');
} else if ($i == $dayInt) {
$flagEnd = $endTimeObj->toDateString();
$startTimeParam = Carbon::parse($flagEnd . ' 08:00:00');
} else {
$flagObj = $startTimeParam->addDay(1);
$flagMid = $flagObj->toDateString();
$startTimeParam = Carbon::parse($flagMid . ' 08:00:00');
$endTimeparam = Carbon::parse($flagMid . ' 22:00:00');
}
$returnData[] = $this->caculateSameDay($startTimeParam, $endTimeparam);
}
return $returnData;
}

//计算同一天的数据
private function caculateSameDay($startTimeObj, $endTimeObj)
{
$startHour = $startTimeObj->hour;
$startMin = $startTimeObj->minute;
$endHour = $endTimeObj->hour;
$endMin = $endTimeObj->minute;
//开始时间大于8点,设置为8点
if ($startHour <= $this->startTime) {
$startHour = $this->startTime;
}
//结束时间小于22点设置 为22点
if ($endHour > $this->endTime) {
$endHour = $this->endTime;
}
$week = $endTimeObj->dayOfWeek;

if ($startMin > 30 && $startMin < 60) {
$startHour++;
}

$data = array();
$data2 = array();
$i=1;
for ($startHour; $startHour <= $endHour; ++$startHour) {
if ($startHour < 12) {
if($startHour<10)
{
$startHour = 0 . $startHour;
}
$data[$i][1] = $startHour . ':' . '00';
$data[$i][2] = $startHour . ':' . '30';
$data[$i][3] = 'morrning';
} elseif ($startHour >= 12 && $startHour < 18) {
$data[$i][1] = $startHour . ':' . '00';
$data[$i][2] = $startHour . ':' . '30';
$data[$i][3] = 'afternoon';
} else if ($startHour >= 18 && $startHour < 22) {
$data[$i][1] = $startHour . ':' . '00';
$data[$i][2] = $startHour . ':' . '30';
$data[$i][3] = 'evening';
}
$i++;
}
if(!empty($data))
{
if($endMin<30)
{
unset($data[$i-1][2]);
}
}
return array('week' => $week, 'date' => $endTimeObj->toDateString(), 'daySchdule' => $data);
}

/**
* @desc 根据判断时间段是否已经被预约
***/
public function checkIsAppointByDate($spaceId,$workUnitId,$date,$time=DiagnoseSchedule::DAYSCHDULE)
{
$isAppoint = DiagnoseSchedule::checkIsAppointByDate($spaceId,$workUnitId,$date,$time);
if($isAppoint->isEmpty())
{
return true;
}
else
{
return false;
}
}

/**
* @desc 根据日期判断是否已经被预约
***/
public function checkIsAppointByWeek($spaceId,$workUnitId,$week,$time)
{
$isAppoint = DiagnoseSchedule::checkIsAppointByWeek($spaceId,$workUnitId,$week,$time);
if($isAppoint->isEmpty())
{
return true;
}
else
{
return false;
}
}

/**
* @desc 生成90天预约数据
**/
public function createNinetySchdules($spaceId)
{
$spaceObj = Space::find($spaceId);
if(empty($spaceObj))
{
return false;
}
$modelDatas = ScheduleModel::where('space_id',$spaceId)
->where('is_stop_diagnose',ScheduleModel::STOPDIAGNOSENO)
->where('is_delete',ScheduleModel::DELETENO)
->get();
if($modelDatas->isEmpty())
{
return false;
}
foreach ($modelDatas as $modelData)
{
$param['space_id'] = $modelData->space_id;
$param['work_unit_id'] = $modelData->work_unit_id;
$param['appoint_date'] = '';
$param['user_id']=$spaceObj->user_id;
$param['work_unit_name']=$modelData->work_unit_name;
$param['day_schdule'] = $modelData->day_schdule;
$param['time'] = $modelData->time;
$param['week'] = $modelData->week;
$param['is_stop_diagnose'] =$modelData->is_stop_diagnose;
$param['is_delete'] =$modelData->is_delete;
$param['number'] =1;
$param['schdule_type'] = DiagnoseSchedule::CYCLE;
$param['model_id']=$modelData->id;
$param['date_time'] = '';
$res = $this->createOperateData($param);
}
return true;
}

/**
* @desc 生成schdule的数据
**/
private function createOperateData($param)
{
$dates = $this->conversionDateByWeek($param['week']);
foreach ($dates as $date)
{
$param['appoint_date'] = $date;
$param['date_time'] = $param['appoint_date'].' '.$param['time'];
$this->addScheduleData($param);
}
return true;
}

/**
* @desc 根据周几来判断生成的时间
**/
public function conversionDateByWeek($week)
{
$nowObj = Carbon::now();
$weekDay = $nowObj->dayOfWeek;
$dayInt = $week-$weekDay;
if( $dayInt!=0)
{
$weekDayCount = 12;
}
else
{
$weekDayCount = 13;
}
$dates = array();
for ($i=1;$i<=$weekDayCount;$i++)
{
$flagDayObj = Carbon::now()->addDay($dayInt);
if($dayInt>0 && $i==1 )
{
$dates[] = $flagDayObj->toDateString();
}
$flagFutureDay = $flagDayObj->addDay($i*7);
$dateString = $flagFutureDay->toDateString();
$dates[] = $dateString;
}
return $dates;
}

/**
* @desc 异步事件处理批量出停诊
**/
public function stopOrStartDiagnose($spaceId,$startTime,$endTime,$diagnoseType)
{
$datas = $this->changeTimeToSchdule($startTime, $endTime);
if($datas['errorCode']!=0)
{
return $datas;
}
foreach ($datas['data'] as $time)
{
$modeWhere['space_id'] = $spaceId;
$schduleWhere['space_id'] = $spaceId;
$date = $time['date'];
$timeLists = $time['daySchdule'];
foreach ($timeLists as $timeList)
{
$diagnoseSchduleObjs = DiagnoseSchedule::where('space_id',$spaceId)
->where('appoint_date',$date)
->whereIn('time',[$timeList[1],$timeList[2]])
->get();
foreach ($diagnoseSchduleObjs as $diagnoseSchduleObj)
{
if(empty($diagnoseSchduleObj))continue;
$diagnoseSchduleObj->is_stop_diagnose = $diagnoseType;
$diagnoseSchduleObj->save();
$this->writeLog('停诊数据:ID'.$diagnoseSchduleObj->id);
if($diagnoseSchduleObj->model_id != 0)
{
$modelObj = ScheduleModel::find($diagnoseSchduleObj->model_id);
$modelObj->is_stop_diagnose = $diagnoseType;
$modelObj->save();
$this->writeLog('停诊模版数据:模版ID'.$modelObj->id);
}
}
}
}
return array('errorCode' => 0, 'errorMessage' => 'success', 'data' =>[]);
}


/**
* @desc 批量停诊按照时间格式停诊
**/
public function stopOrStartDiagnoseBySchdule($spaceId,$startTime,$endTime,$timeSchdule,$diagnoseType)
{
$startDate = Carbon::parse($startTime);
$endDate = Carbon::parse($endTime);//->toDateString();
$int = $startDate->diffInDays($endDate,true);
$operateSchdules = $this->getTimeSchdules($timeSchdule);
for($i=0;$i<=$int;$i++)
{
$flag = Carbon::parse($startTime);
$flagDate = $flag->addDay($i);
$date = $flagDate->toDateString();
//批量出停诊
$this->addStopDiagnoseDatas($spaceId,$date,$operateSchdules,$diagnoseType);
$res = DiagnoseSchedule::batchStop($spaceId,$date,$operateSchdules,$diagnoseType);
if(false === $res)
{
Log::error('停诊数据失败,spaceId:'.$spaceId.'startTime'.$startTime.'endTime'.$endTime,$operateSchdules);
return false;
}
}
return true;
}

private function addStopDiagnoseDatas($spaceId,$date,$operateSchdules,$diagnoseType)
{
if(empty($spaceId) || empty($date) || empty($operateSchdules))
{
return false;
}
foreach ($operateSchdules as $operateSchdule)
{
$param['space_id'] = $spaceId;
$param['time'] = $operateSchdule;
$param['appoint_date'] = $date;
if($diagnoseType == 1)
{
StopDiagnose::firstOrCreate($param,$param);
}
else
{
StopDiagnose::where($param)->delete();
}

}
return true;
}

private function getTimeSchdules($timeSchdules)
{
if(empty($timeSchdules))
{
return array_merge(ScheduleModel::MORRNINTSCHDULE,ScheduleModel::AFTRENOONSCHDULE,ScheduleModel::EVENINGSCHDULE);
}
$returnData = array();
foreach ($timeSchdules as $timeSchdule)
{
$returnData = array_merge($returnData,ScheduleModel::DAYSCHEULD[$timeSchdule]);
}
return $returnData;
}

/**
* @desc 停诊时判断是否存已经预约的数据
* @return true 可以停诊 false 不可以停诊
***/
public function checkBatchStopDiagnose($spaceId,$startTime,$endTime,$timeSchdule)
{
$startDate = Carbon::parse($startTime);
$endDate = Carbon::parse($endTime);//->toDateString();
$int = $startDate->diffInDays($endDate,true);
$operateSchdules = $this->getTimeSchdules($timeSchdule);
for($i=0;$i<$int;$i++)
{
$flag = Carbon::parse($startTime);
$flagDate = $flag->addDay($i);
$date = $flagDate->toDateString();

//批量出停诊
$res = DiagnoseSchedule::checkBatchStop($spaceId,$date,$operateSchdules);

if(false == $res)
{
return false;
}
}
return true;
}

/**
* @desc 停诊时判断是否存已经预约的数据
* @return true 可以停诊 false 不可以停诊
***/
public function checkStopDiagnose($spaceId,$startTime,$endTime)
{
$checkCanStop = DiagnoseSchedule::checkOneIsAppoint($spaceId,$startTime,$endTime);
if($checkCanStop->isNotEmpty())
{
return false;
}
else
{
return true;
}
}

/***
* @desc 删除
**/
public function deleteDiagnose($diagnoseSchduleId)
{
$scheduleObj = DiagnoseSchedule::find($diagnoseSchduleId);
if(empty($scheduleObj))
{
return false;
}
if($scheduleObj->schdule_type == DiagnoseSchedule::INTERIM)
{
$scheduleObj->is_delete = DiagnoseSchedule::DELETEYES;
$scheduleObj->save();
return true;
}
else
{
$schduleObjs = DiagnoseSchedule::where('space_id',$scheduleObj->space_id)
->where('work_unit_id',$scheduleObj->work_unit_id)
->where('week',$scheduleObj->week)
->where('time',$scheduleObj->time)
->get();
if($schduleObjs->isEmpty())
{
return false;
}
$modelId = 0;
foreach ($schduleObjs as $schduleObj)
{
$schduleObj->is_delete = DiagnoseSchedule::DELETEYES;
$schduleObj->save();
$modelId = $schduleObj->model_id;
$this->writeLog('删除数据:ID'.$schduleObj->id);
}
$modelObj = ScheduleModel::find($modelId);
if(!empty($modelObj))
{
$modelObj->is_delete = ScheduleModel::DELETEYES;
$modelObj->save();
$this->writeLog('删除模版数据:模版ID'.$modelObj->id);
}
return true;
}
}

/**
* @desc 判断是否可以删除
* @param $diagnoseSchduleId
* @return true 可以删除 false 不可以删除
**/
public function checkIsDelete($diagnoseSchduleId)
{
$scheduleObj = DiagnoseSchedule::find($diagnoseSchduleId);
if(empty($scheduleObj))
{
return false;
}
if($scheduleObj->schdule_type == DiagnoseSchedule::INTERIM)
{
if($scheduleObj->number < 1 )
{
return false;
}
else
{
return true;
}
}
else
{
$schduleObjs = DiagnoseSchedule::where('space_id',$scheduleObj->space_id)
->where('work_unit_id',$scheduleObj->work_unit_id)
->where('week',$scheduleObj->week)
->where('time',$scheduleObj->time)
->where('number', '<', 1)
->get();
if($schduleObjs->isEmpty())
{
return true;
}
else
{
return false;
}
}
}


}

10、总结

这是市面上最简单的一个预约版本,简单如商品上下架。
使用的算法:枚举算法,

优点:医生库存可控,方便时间管理,

缺点:先生成数据集,修改删除都是批量处理,可塑性比较低

二、升级版

所谓的升级版就是加各种干扰因子。

后台生成可约时间,依赖项目时长,机构营业时间,机构班次,项目麻醉时间

用户预约时,除了选择医生、时间外,还要选择项目;通过选择的项目,来获取对应的时间号源

1、香蜜丽格预约香蜜

​ 根据机构的营业时间+机构下项目时长+项目是否麻醉等约束条件,生成未来固定一段时间的医生相关联的每个项目可约模板。每天按照模板生成 医生+项目时长+项目麻醉类型+可约时间段+日期+时间 的可约数据

2、设计实现

  • 后台生成医生的每个项目的出诊时间模板
  • 依赖时间模板定时生成未来给定天预约数据
  • 前端根据生成的预约数据展示给用户可约数据

3、技术原理

​ 生成器模式+试探算法

4、干扰因子

  • 机构
  • 项目治疗时长
  • 项目麻醉时长
  • 机构排班
  • 可约周期
  • 出诊类型
  • 同一时间点可约个数

5、设计图

image-20210602164750754

7、后台界面

image-20210602165259216

8、前端预约页面

image-20210602165229119

9、总结

有点:一次性生成医生项目可约数据,方便查看

缺点:约束性条件多生成预约数据出错概率大,约束条件变更需要批量修改已经生成的可约数据,比较局限

三、铂金版

1、背景

共享医院,有手术,光电,注射,口腔 四种类型的手术室,每种类型手术室有多个,

相关项目手术室 有关键设备影响,比如M22 ,黄金微针 ,舒敏之星, 当前时间是否被占用,如果占用手术室不可约

相关项目手术室 人员占用情况影响 比如注射配台护士,光电配台护士 当前时间是否空闲,如果没有空闲,手术不可约

手术室预约要求至少30分钟,

会议室重叠算法

2、技术实现原理

(1)根据手术时长(比如1小时) 把一天在班时间[8:00-20:00]切割成小时间段比如[8:00-9:00]….[19:00-20:00]

(2)获取每个手术室每天可约的时间间隔(枚举算法)

(3)循环(1)每天可约时间和(2)中每个手术室可约时间做比较取出可约手术室

(4)可约器材和可约人员 循环(2)(3)步骤

(5)生成可约的时间和日期列表

3、算法

​ 工厂模型 + 贪心算法

4、设计图

5、代码

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
<?php

namespace App\Models;

use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;

class EyRoomUsage extends Model
{
protected $table = "ey_room_usages";
protected $guarded = [];

const WEEK_MON = 1;//周一
const WEEK_TUES = 2;//周二
const WEEK_WED = 3;//周三
const WEEK_THUR = 4;//周四
const WEEK_FRI = 5;//周五
const WEEK_SAT = 6;//周六
const WEEK_SUN = 0;//周日

const DAYSCHDULE = [
'9:00', '9:30', '10:00', '10:30', '11:00', '11:30', '12:00', '12:30',
'13:00', '13:30', '14:00', '14:30', '15:00', '15:30', '16:00', '16:30',
'17:00', '17:30', '18:00', '18:30', '19:00', '19:30', '20:00',
];
const SCHDULE_NAME_ARR = [
'morrning' => '上午',
'afternoon' => '下午',
'evening' => '晚上'
];
const WEEK_NAME_ARR = [
self::WEEK_MON => '星期一',
self::WEEK_TUES => '星期二',
self::WEEK_WED => '星期三',
self::WEEK_THUR => '星期四',
self::WEEK_FRI => '星期五',
self::WEEK_SAT => '星期六',
self::WEEK_SUN => '星期日',

];

const SOURCE_TYPE_ROOM = 'room';//类型为手术室
const SOURCE_TYPE_RESOURCE = 'resource';//类型为资源

const IS_DELETE_YES = 1;//删除
const IS_DELETE_NO = 0;//正常
//手术室间数
public static $anaesthesiaRoomNum = [
EyAppointmentSurgery::ANAESTHESIA_GENERAL => 6,//全麻
EyAppointmentSurgery::ANAESTHESIA_LOCATION => 8,//局麻
];


public static function createEyRoomUsage($data)
{
if (empty($data['appoint_date']) || empty($data['source_id'])) {
return false;
}
$obj = new self();
isset($data['source_type']) ? $obj->source_type = $data['source_type'] : '';
isset($data['source_id']) ? $obj->source_id = $data['source_id'] : '';
isset($data['duration']) ? $obj->duration = $data['duration'] : '';
isset($data['appoint_date']) ? $obj->appoint_date = $data['appoint_date'] : '';
isset($data['start_time']) ? $obj->start_time = $data['start_time'] : '';
isset($data['end_time']) ? $obj->end_time = $data['end_time'] : '';
isset($data['sort_room_num']) ? $obj->sort_room_num = $data['sort_room_num'] : '';
$obj->is_delete = self::IS_DELETE_NO;
$obj->save();
return $obj;

}

public static function cancelEyRoom($id)
{
$obj = self::find($id);
if (false == $obj instanceof self) {
return false;
}
$obj->is_delete = self::IS_DELETE_YES;
$obj->save();
return true;
}

//创建预约时检测当前时间是否可约
public static function checkCanCreateRoomUsage($timeList, $date, $sourceId, $sourceType = self::SOURCE_TYPE_ROOM)
{
if (empty($timeList) || empty($date) || empty($sourceId)) {
return ['errCode' => -1, 'errMsg' => '参数非法', 'roomId' => 0];
}
$dataListObj = self::where('source_type', $sourceType)
->where('source_id', $sourceId)
->where('appoint_date', '=', $date)
->where("is_delete", self::IS_DELETE_NO)
->get();
$res = $dataListObj->groupBy("sort_room_num");
$eyOperRoomPoolObj = EyOperationRoomPool::find($sourceId);
$souceNum = $eyOperRoomPoolObj->room_num;
$hasAppoitRoomPools = self::getPoolAppointData($res->toArray(), $souceNum);
if (empty($hasAppoitRoomPools)) {
$hasAppoitRoomPools = [1 => self::DAYSCHDULE];
}
[$checkappoint, $roomId] = self::timeFactory($hasAppoitRoomPools, $timeList);
if (false == $checkappoint) {
return ['errCode' => -1, 'errMsg' => '时间不可约', 'roomId' => 0];
} else {
return ['errCode' => 0, 'errMsg' => '', 'roomId' => $roomId];
}
}


/**
* @desc 获取预约列表
* $timeLong 时间*2
* $startTime 日期
* @param
***/
public static function getScheduleList($timeLong, $startTime, $endTime, $surgeryType, $poolId, $anaesthesiaType)
{
$timeLists = self::getScheduleModelTitle($startTime, $endTime);
$hasAppointDateList = self::getRoomUsageList($startTime, $endTime, $surgeryType, $poolId, self::SOURCE_TYPE_ROOM, $anaesthesiaType);
foreach ($timeLists as $key => $timeList) {
if (isset($hasAppointDateList[$key])) {
$timeLists[$key]['timeSchdule'] = self::timeFactoryList($hasAppointDateList[$key], $timeLong, $surgeryType);
} else {
$timeLists[$key]['timeSchdule'] = self::timeFactoryList([1 => self::DAYSCHDULE], $timeLong, $surgeryType);//如果手术室或者资源没有被约的默认取第一个
}
}
return $timeLists;
}

/**
* @desc 判断是否忙碌可约
* $currentRoomNum 手术室剩余可约间数
* $surgeryType 手术室类型
* 剩余可约手术室间数 >=(手术室总间数)*0.3 空闲可约
* 否则 忙碌可约
**/
public static function isBusyAppoint($currentRoomNum, $surgeryType)
{
if ($currentRoomNum <= 0) {
return false;
}
$obj = EyOperationRoomPool::where('room_type', $surgeryType)->first();
if (false == $obj instanceof EyOperationRoomPool) {
return false;
}
$threshold = round($obj->room_num * 0.3);
if ($currentRoomNum >$threshold) {
return true;
} else {
return false;
}
}

//生成每天可以预约的数据
public static function timeFactoryList($timeLists, $timeLog, $surgeryType = '')
{
$return = [];
if ($timeLog < 1) {
return [];
}
$listDatas = self::DAYSCHDULE;
$canAppointDate = [];
foreach ($listDatas as $key => $listData) {
$flag = [];
for ($i = $key; $i <= $key + $timeLog; $i++) {
if (isset($listDatas[$i])) {
$flag[] = isset($listDatas[$i]) ? $listDatas[$i] : '22:00';
}
}
if (!empty($flag) && 1 != count($flag)) {
[$checkappoint, $roomId, $busystatus] = self::timeFactory($timeLists, $flag, $surgeryType);
if (true == $checkappoint) {
$oktime = [];
$oktime['time'] = $flag;
$oktime['status'] = $busystatus;
$canAppointDate[] = $oktime;
}
}
}
$res = self::addTimeNode($canAppointDate);
return $res;;
}

//生成上下午时间节点
public static function addTimeNode($timeLists)
{
if (empty($timeLists)) {
return [
'morrning' => [],
'afternoon' => []
];
}
$morrn = [];
$afternoon = [];
foreach ($timeLists as $data) {
$timeList = $data['time'];
$startTime = current($timeList);
$nodeKey = array_search('13:00', self::DAYSCHDULE);;
$startTimeTimeKey = array_search($startTime, self::DAYSCHDULE);
if ($startTimeTimeKey < $nodeKey) {
$morrn[] = $data;
} else {
$afternoon[] = $data;
}
}
return [
'morrning' => $morrn,
'afternoon' => $afternoon
];
}


public static function timeFactory($timeLists, $toConfirmData, $surgeryType = '')
{
$count = count($toConfirmData);
sort($toConfirmData);
$checkedRoomId = 0;
$canCheckNum = 0;
foreach ($timeLists as $roomId => $timeList) {
if (count($timeList) != count($timeList, 1))//判断是二维数组
{
foreach ($timeList as $list) {
$res = array_diff($list, $toConfirmData);
$res2 = array_diff($toConfirmData, $list);

if (!empty($res) && $res != $list && count($list) > $count && empty($res2)) {
if (0 == $checkedRoomId) {
$checkedRoomId = $roomId;
}
$canCheckNum++;
//return [true, $roomId];
}
sort($list);
if ($list == $toConfirmData) {
if (0 == $checkedRoomId) {
$checkedRoomId = $roomId;
}
$canCheckNum++;
// return [true, $roomId];
}
}
} else {
$res = array_diff($timeList, $toConfirmData);
$res2 = array_diff($toConfirmData, $timeList);
if (!empty($res) && $res != $timeList && count($timeList) > $count && empty($res2)) {
return [true, $roomId, true];
}
sort($timeList);
if ($timeList == $toConfirmData) {
return [true, $roomId, true];
}
}
}

if (0 != $checkedRoomId && $canCheckNum > 0) {
$isBusy = self::isBusyAppoint($canCheckNum, $surgeryType);
return [true, $checkedRoomId, $isBusy];
} else {
return [false, 0, false];
}
}

//获取可以预约的列表
public static function getRoomUsageList($startDate, $endDate, $surgeryType, $sourceId, $sourceType = self::SOURCE_TYPE_ROOM, $anaesthesiaType = '')
{
if ($sourceType == self::SOURCE_TYPE_ROOM) {
$eyOperRoomPoolObj = EyOperationRoomPool::find($sourceId);
if (empty($anaesthesiaType) || EyAppointmentSurgery::SURGE_TYPE_SS != $surgeryType) {
$souceNum = $eyOperRoomPoolObj->room_num;
} else {
if ($anaesthesiaType == EyAppointmentSurgery::ANAESTHESIA_GENERAL) {
$souceNum = self::$anaesthesiaRoomNum[$anaesthesiaType];
} else {
$souceNum = $eyOperRoomPoolObj->room_num;
}
}

} else {
//TODO 获取资源数量
$souceNum = 0;
}
$dataLists = self::where('source_type', $sourceType)
->where('source_id', $sourceId)
->where('appoint_date', '>=', $startDate)
->where('appoint_date', '<=', $endDate)
->where("is_delete", self::IS_DELETE_NO)
->get();
$dataObjs = $dataLists->groupBy('appoint_date');
$appointRoomList = [];
if (!empty($dataObjs)) {
foreach ($dataObjs as $key => $dataObj) {
$res = $dataObj->groupBy("sort_room_num");
$appointRoomList[$key] = self::getPoolAppointData($res->toArray(), $souceNum);
}
}
return $appointRoomList;
}

//生成每个手术室每天可预约的时间点
public static function getPoolAppointData($datas, $souceNum)
{
if ($souceNum < 0) {
return [];
}
$return = [];
for ($i = 1; $i <= $souceNum; $i++) {
if (isset($datas[$i])) {
$return[$i] = self::getRoomAppoint($datas[$i]);
} else {
$return[$i][] = self::DAYSCHDULE;
}
}
return $return;

}

public static function getRoomAppoint($datas)
{
$timeListKeys = array_flip(self::DAYSCHDULE);
$timeListDatas = self::DAYSCHDULE;
$flagArr = [];
foreach ($datas as $data) {
$startTime = $data['start_time'];
$endTime = $data['end_time'];
if (empty($flagArr)) {
if (array_key_exists($startTime, $timeListKeys) && array_key_exists($endTime, $timeListKeys)) {
$flagArr = self::splitArrByKey($startTime, $endTime, $timeListDatas);
}
} else {
$flagArr = self::splitArrByKeyPlus($startTime, $endTime, $flagArr);
}
}
return $flagArr;
}

public static function splitArrByKeyPlus($startTime, $endTime, $ruleArrs)
{
$return = $ruleArrs;

foreach ($ruleArrs as $key => $ruleArr) {
if (in_array($startTime, $ruleArr) && in_array($endTime, $ruleArr)) {
$flagArr = self::splitArrByKey($startTime, $endTime, $ruleArr);
if (!empty($flagArr)) {
unset($return[$key]);
$return = array_merge($return, $flagArr);
}
}
}
return $return;
}

//拆分数组
public static function splitArrByKey($startTime, $endtime, $ruleArrs)
{
$startKey = array_search($startTime, $ruleArrs);
$endKey = array_search($endtime, $ruleArrs);
$startArr = array_slice($ruleArrs, 0, $startKey + 1);
$endArr = array_slice($ruleArrs, $endKey);
$return = [];
if (!empty($startArr) && 1 != count($startArr)) {
$return[] = $startArr;
}
if (!empty($endArr) && 1 != count($endArr)) {
$return[] = $endArr;
}
return $return;
}

public static function getScheduleModelTitle($startTime, $endTime)
{
$date = new Carbon($startTime);
$count = $date->diffInDays($endTime);
$i = 0;
$return = [
$date->toDateString() => [
'date' => $date->toDateString(),
'weekDay' => $date->dayOfWeek,
'week' => EyRoomUsage::WEEK_NAME_ARR[$date->dayOfWeek]
]
];
for ($i = 0; $i < $count; $i++) {
$flagDate = $date->addDay(1);
$return[$flagDate->toDateString()] = [
'date' => $flagDate->toDateString(),
'weekDay' => $flagDate->dayOfWeek,
'week' => EyRoomUsage::WEEK_NAME_ARR[$flagDate->dayOfWeek]
];
}
return $return;
}

//判断领建同步过来的预约是否消耗手术室预约
public static function checkLJAppointTime($startDateTime, $endDateTime)
{
$startCarbonObj = Carbon::parse($startDateTime);
$endCarbonObj = Carbon::parse($endDateTime);
$int = $startCarbonObj->diffInMinutes($endCarbonObj);
//可约时长最长是8小时
if ($int > 480) {
return [];
}
$list = [];
while ($startCarbonObj->lte($endCarbonObj)) {
$list[] = $startCarbonObj->hour . ':' . (0 == $startCarbonObj->minute ? '00' : $startCarbonObj->minute);
$startCarbonObj->addMinute(30);
}
return ['timeList' => $list, 'timeduration' => $int / 60, 'date' => $startCarbonObj->toDateString()];
}


}

6、前端页面图

image-20210603161949165

image-20210603162003366

7、总结

优点:只是依赖规则,依赖因子都是可插拔模式,扩展性比较好,可塑性高

确定:每次获取批量可约数据计算量大,枚举法比较暴力时间复杂度高(网上有会议室重叠算法和堆算法)

epoll 的内部实现原理【上】

wwf 发表于 2021-03-29 | 分类于 操作系统 , linux
1
最近看了几天 IO多路复用,epoll相关, 理论明白了大概,看的脑瓜疼,先写个文章记录一下。

一、关键词

  • IO多路复用
  • epoll select poll
  • mmap 红黑树 连表
  • 中断程序

    二、epoll

    1、epoll概念

    epoll 接口是解决Linux内核处理大量文件描述符提出的方案。

场景:100万用户同事与一个TCP保持连接,而每一时刻只有几个或者几十个连接是活跃的(接受TCP包);进程收集有事件的连接时,把这100万套接字传递给炒作系统,由操作系统内核计算连接上有没有未处理的事件(select 和 poll的做法,极大的浪费了资源),而epoll在linux内核中申请了一个简单的文件系统,在进程启动时创建一个epoll对象,并在需要时候向它添加删除。

2、epoll基本思想

  • epoll在Linux内核中采用红黑树构建了文件系统,。
  • epoll红黑树上采用事件异步唤醒,内核监听IO,事件发生后内核搜索红黑树并将对应节点数据放入异步唤醒的事件队列中
  • epoll的数据从用户空间到内核空间采用mmap存储I/O映射来加速(传递最快,消耗最小,传递数据过程不涉及系统调用)

二、epoll如何工作

epoll三个方法

  • 调用epoll_create创建一个epoll 对象(epoll文件系统中给这个句柄分配资源)
  • 调用epoll_ctl向epoll对象中添加连接的套接字
  • 调用epoll_wait收集发生事件的连接

    调用epoll_create时,内核除了帮我们在epoll文件系统里建了个file结点,在内核cache里建了个红黑树用于存储以后epoll_ctl传来的socket外,还会再建立一个rdllist双向链表,用于存储准备就绪的事件,当epoll_wait调用时,仅仅观察这个rdllist双向链表里有没有数据即可。有数据就返回,没有数据就sleep,等到timeout时间到后即使链表没数据也返回。所以,epoll_wait非常高效

    三、epoll两种触发方式

  • ET模式(边缘触发)只有数据到来才触发,不管缓存区中是否还有数据,缓冲区剩余未读尽的数据不会导致epoll_wait返回
  • LT 模式(水平触发,默认)只要有数据都会触发,缓冲区剩余未读尽的数据会导致epoll_wait返回。
    。。。。。。对外来说有点难啃了一天半时间

参考网上案例写的代码

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
#include <stdio.h>
#include <unistd.h>
#include <sys/epoll.h>

int main(void)
{
int epfd,nfds;
int epfd,nfds;
struct epoll_event ev,events[5]; //ev用于注册事件,数组用
于返回要处理的事件
epfd = epoll_create(1); //只需要监听一个描述>符——标准输入
ev.data.fd = STDIN_FILENO;
ev.events = EPOLLIN; //监听读状态同时设置LT模
式
epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &ev); //注册epoll事件
for(;;)
{
nfds = epoll_wait(epfd, events, 5, -1);
int i;
for( i=0; i < nfds; i++)
{
if(events[i].data.fd==STDIN_FILENO)
{
char buf[1024] = {0};
read(STDIN_FILENO, buf, sizeof(buf));
printf("welcome to epoll's word!\n");
}
}
}
}

运行结果

参考文献:

趣谈linux操作系统

tcp/ip 协议栈——epoll 的内部实现原理

彻底学会使用epoll(一)——ET模式实现分析

Linux下的I/O复用与epoll详解

弱类型语言携手Mysql隐式转换带来的阵痛

wwf 发表于 2021-03-24 | 分类于 后端

背景


程序(php写)中的一个变量传入mysql的where条件查询;发现变量改变查询的结果不变。

解析sql原生语句:

1
select * from `flow_data_logs` where `msgid` = 258041935053193246 limit 1;
1
的结果  msgid 不是 258041935053193246

改写sql:

1
select * from `flow_data_logs` where `msgid` = '258041935053193246' limit 1;
1
的结果  msgid 是 258041935053193246

心路历程


mysql 隐式转换和php弱类型带来多次坑,但是每次依旧采的很欢乐。
本次查询耗时接近两个小时复盘如下:

  1. 发现问题,想办法去定位
  2. 先pull代码重启进程,发现无效
  3. 加断点日志,添加参数和pid,发现数据是先被读出来然后才能写进去,而且是同一个进程,懵逼了
  4. 排除代码有没有在另外机器上执行
  5. 仔细查看日志发现查出的msgid和传入的msgid不是一条数据
  6. 定位到问题,担心是不是超出integer范围溢出了,查询之后排除
  7. 传入参数强制转换成string,问题解决;查bug两小时解决bug两秒钟。

也说php弱类型语言


PHP 弱类型语言变量在定义中不需要明确的类型定义,变量类型是根据使用变量的使用变量的上下文决定,也就是说,如果把一个 string 值赋给变量 $var,$var 就成了一个 string。如果又把一个integer 赋给 $var,那它就成了一个integer

1、php中的整型
  • 整型数的字长和平台有关,且 PHP 不支持无符号的 integer
  • 最大值常量 PHP_INT_MAX ,最小值常量 PHP_INT_MIN (需PHP大于 7.0.0)表示
  • 32位平台下 最大 2147483647 64位平台最大9223372036854775807
  • 如果给定的数超出了integer 的范围,将会被解释为 float

示例64位系统下的常量

1
2
3
4
5
❯ php -r "echo PHP_INT_MAX;"
9223372036854775807%

❯ php -r "echo PHP_INT_MIN;"
-9223372036854775808%

示例 64位系统下的整数溢出

1
2
❯ php -r "var_dump(9223372036854775808);"
float(9.2233720368548E+18)
2、php类型转换

php 中类型强制转换:在要转换的变量之前加上用括号括起来的目标类型

  • (int), (integer) - 转换为整形 integer
  • (bool), (boolean) - 转换为布尔类型 boolean
  • (float), (double), (real) - 转换为浮点型 float
  • (string) - 转换为字符串 string
  • (array) - 转换为数组 array
  • (object) - 转换为对象 object
  • (unset) - 转换为 NULL

【 tips】 (unset) 转换在 PHP 7.2.0 中已被废弃。请注意 (unset) 转换等于将值赋予 NULL。(unset) 转换将在 PHP 8.0.0 中被移除

再说 MySQL 的隐式转换


1、MySQL 中对隐式转换的定义:

When an operator is used with operands of different types, type conversion occurs to make the operands compatible. Some conversions occur implicitly.

当操作符与不同类型的操作数一起使用时,会发生类型转换以使操作数兼容。

2、比较操作时 MySQL 隐式类型转换规则
  • 两个参数至少有一个是 NULL 时,比较的结果也是 NULL,例外是使用 <=> 对两个 NULL 做比较时会返回 1,这两种情况都不需要做类型转换
  • 两个参数都是字符串,会按照字符串来比较,不做类型转换
  • 两个参数都是整数,按照整数来比较,不做类型转换
  • 十六进制的值和非数字做比较时,会被当做二进制串
  • 有一个参数是 TIMESTAMP 或 DATETIME,并且另外一个参数是常量,常量会被转换为 timestamp
  • 有一个参数是 decimal 类型,如果另外一个参数是 decimal 或者整数,会将整数转换为 decimal 后进行比较,如果另外一个参数是浮点数,则会把 decimal 转换为浮点数进行比较
  • 所有其他情况下,两个参数都会被转换为浮点数再进行比较

示例int 转换成浮点数 sql

1
2
3
4
5
6
7
8
SELECT '258041935053193233' = 258041935053193246;
# 结果 是 1

SELECT cast('258041935053193233' + 0.0 as unsigned);
# 结果 258041935053193248

SELECT cast('258041935053193246' + 0.0 as unsigned);
# 结果 258041935053193248
3、参考文献:

MYSQL官网文档

小米信息部技术团队

回归问题本质


  1. php 拿到值为整数的$msgid变量,变量类型为int且没有溢出
  2. php 把int类型的 $msgid传给myslq作为 where msgid=$msgid 条件(myslq 中msg为varchar类型)
  3. mysql 把int msgid转换成float
  4. 查出的结果异常

go reload

发表于 2021-03-15 | 分类于 go

关键词预热

  • 热启动
  • 进程间通信
  • 信号量
  • 信号
  • 套接字

    问题背景

    重启服务或者使用kill -9 或者control + c 不可避免会遇到下面两个问题

  1. 未处理完的请求终端,破坏数据一致性
  2. 新服务启动期间,请求无法进来导致服务一段时间不可用

解决方案

  • 生产环境应用层通过四层或者七层负载均衡,通过流量调度来实现平滑重启【待更新】
  • 通过Kubernetes实现对Go服务进行扩容、更新、回滚、平滑重启【待更新】
  • 进程间通信来解决热更新【下文介绍】

    生产环境集群一般的做法是 ApiGateway + CD, 发布的时候自动摘除机器,等待程序处理完现有请求再做发布处理,也可以借助SLB或者LVS手动切换流量

    实现原理

  1. 原(父)进程fork一个子进程,同时让子进程继承父进程监听的所有socket
  2. 子进程初始化后开始接收新的请求
  3. 父进程停止接收新的请求,处理完当下请求后,等服务空闲,平滑退出

    服务升级/更新时不关闭现有连结,不会出现拒绝访问的情况,对用户友好/用户无感知

进程间通信来解决热更新两种方式

  1. 设置套接字,将多个socket绑定在同一个监听端口,复制套接字【学习中】
  2. 用父子进程fork-exec继承文件描述符的特性,在父子进程之间维护传递监听socket。在升级/重启的过程中,父进程将监听socket继承给子进程,使得整个过程没有监听 socket 被关闭,从而不产生拒绝服务的问题。【本文demo使用方式】

实现方式

常见的开源实现graceful和endless。

graceful:https://github.com/facebookgo/grace

endless:https://github.com/fvbock/endless

Golang >= 1.8可以使用 http.Server 的 Shutdown 方法实现
##代码实现

endless方式实现,lesof + 端口 查看启动的进程
kill -1 +端口 发送请求是查看请求被fork子进程收

1
2
3
4
5
6
7
8
9
10
11
12
13
14
endless.DefaultReadTimeOut = setting.ReadTimeout
endless.DefaultWriteTimeOut = setting.WriteTimeout
endless.DefaultMaxHeaderBytes = 1 << 20
endPoint := fmt.Sprintf(":%d",setting.HTTPPort)

server := endless.NewServer(endPoint,routers.InitRouter())
server.BeforeBegin = func(add string) {
log.Printf("Actual pid is %d",syscall.Getpid())
}

err := server.ListenAndServe()
if err != nil {
log.Printf("server err : %v",err)
}

http.Serve 实现demo,kill -1 +端口 同时发送请求,请求没有中断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
go func() {
if err := s.ListenAndServe();err !=nil{
logs.Info("Listen: %s\n", err)
}
}()

//监听所有信号
quit := make(chan os.Signal)
signal.Notify(quit,os.Interrupt)
log.Println("PID:",os.Getppid())
<- quit
log.Println("shut down server .....",<- quit)
ctx,cancel := context.WithTimeout(context.Background(),5*time.Second)
defer cancel()
if err := s.Shutdown(ctx);err != nil {
log.Fatal("server shutdown :",err)
}
log.Println("Server exiting")

unix基础(补充)

进程间通信场景

  • 数据传输
  • 共享数据
  • 通知事件
  • 资源共享
  • 进程控制

    进程之间通信方式

  • 管道
  • 信号量:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段
  • 消息队列
  • 信号:信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生【本文demo采用的方式】
  • 共享内存
  • 套接字

信号说明

脚本输入:++kill -l++


常用信号列表

命令行实现 start stop reload

wechaty

发表于 2021-03-10

Hello World

发表于 2021-03-10

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment

7 日志
5 分类
5 标签
© 2021 bangbang
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4