2024年4月

前言

比方说我们有以下两个类:

 public class Class
 {
     public string? Name { get; set; }
     public List<Student>? Students { get; set; }
 }
 public class Student
 {
     public string? Name { get; set; }
 }

一个表示班级,一个表示学生,一个班级包含多个学生。在WPF中我们该如何显示这种具有层级关系的数据呢?

今天给大家介绍的是用
TreeView

HierarchicalDataTemplate
进行显示。

实现效果如下所示:

效果

如果你对此感兴趣,可以接着往下阅读。

创建数据

 private void Button_Click(object sender, RoutedEventArgs e)
 {
     Student student1 = new Student() { Name = "小明" };
     Student student2 = new Student() { Name = "小红" };
     Student student3 = new Student() { Name = "小黄" };
     Student student4 = new Student() { Name = "小绿" };
     Student student5 = new Student() { Name = "小刚" };

     List<Student> students1 = new List<Student>()
     {
         student1,
         student2,
         student3
     };

     List<Student> students2 = new List<Student>()
     {              
         student4,
         student5
     };

     Class class1 = new Class()
     {
         Name = "班级1",
         Students = students1
     };

     Class class2 = new Class()
     {
         Name = "班级2",
         Students = students2
     };

     List<Class> classes = new List<Class>()
     {
         class1,
         class2
     };

     DataContext = classes;

 }

数据模板的使用

xaml:

  <TreeView>
      <TreeViewItem ItemsSource="{Binding}" Header="全部班级"/>
  </TreeView>

HierarchicalDataTemplate
介绍

HierarchicalDataTemplate

WPF(Windows Presentation Foundation)
中的一种数据模板,用于在树状结构或层次结构中显示数据。它允许您定义如何呈现包含子项的数据对象。

通过
HierarchicalDataTemplate
,您可以指定一个模板,用于呈现数据对象本身,以及一个模板,用于呈现其子项。这使得在TreeView等控件中轻松显示复杂的数据结构,如文件夹和文件、组织架构等。

通常,您会在
ItemsSource
属性中指定数据源,然后使用
HierarchicalDataTemplate
定义每个级别的数据对象应该如何呈现。

通过使用
HierarchicalDataTemplate
,您可以更灵活地控制数据的呈现方式,使您能够创建具有深层次结构的动态UI。

HierarchicalDataTemplate
的使用

xaml:

<Window.Resources>
    <HierarchicalDataTemplate DataType = "{x:Type local2:Class}"
       ItemsSource = "{Binding Path=Students}">
        <TextBlock Text="{Binding Path=Name}"/>
    </HierarchicalDataTemplate>

    <DataTemplate DataType="{x:Type local2:Student}">
        <TextBlock Text="{Binding Path=Name}"/>
    </DataTemplate>
</Window.Resources>

我们可以发现对于
Class
类,使用了一个
HierarchicalDataTemplate

ItemsSource
绑定的是
Class
类的
Students
属性。

Student
类并没有再含有层次数据了所有直接使用
DataTemplate
就好了。

查看实现效果

最后实现的效果如下所示:

效果

总结

在日常开发过程中,我们可能也会有显示层级数据的需求,本文通过一个简单的Demo,介绍了在WPF中通过
TreeView控件

HierarchicalDataTemplate层级数据模板
进行层级数据的显示。希望对正在学习WPF或者对WPF感兴趣的同学有所帮助。

接下来我将持续更新“深度解读《深度探索C++对象模型》”系列,敬请期待,欢迎关注!也可以关注公众号:iShare爱分享,自动获得推文和全部的文章列表。

前面两篇请通过这里查看:

深度解读《深度探索C++对象模型》之数据成员的存取效率分析(一)

深度解读《深度探索C++对象模型》之数据成员的存取效率分析(二)

这一节讲解具体继承的情况,具体继承也叫非虚继承(针对虚继承而言),分为两种情况讨论:单一继承和多重继承。

单一继承

在上面的例子中,所有的数据都封装在一个类中,但有时可能由于业务的需要,需要拆分成多个类,然后每个类之间具有继承关系,比如可能是这样的定义:

class Point {
	int x;
};
class Point2d: public Point {
	int y;
};
class Point3d: public Point2d {
	int z;
};

对于这样的单一继承关系,在前面的文章
《深度解读《深度探索C++对象模型》之C++对象的内存布局》
中已经分析过了。一般而言,
Point3d
类的内存布局跟独立声明的类的内存布局没什么差别,除非在某些情况下,编译器为了内存对齐而进行填充,造成空间占用上会变大的情况,但对于存取效率而言没什么影响,因为在编译期间就已经确定好了它们的偏移值。完善上面的例子,在
main
函数中定义
Point3d
的对象,然后访问各个成员,看看对应的汇编代码。

int main() {
    printf("&Point2d::y = %d\n", &Point2d::y);
    printf("&Point3d::y = %d\n", &Point3d::y);
    Point3d p3d;
    p3d.x = p3d.y = p3d.z = 1;

    return 0;
}

上面两行打印代码输出的都是
4
,再看看第5行代码对应的汇编代码:

mov     dword ptr [rbp - 8], 1
mov     dword ptr [rbp - 12], 1
mov     dword ptr [rbp - 16], 1

生成的汇编代码跟独立类的汇编代码没有区别,这说明单一继承的存取效率跟没有继承关系的类的存取效率是一样的。

多重继承

或许业务需要,继承关系不是上面的单一继承关系,而是需要改成多重继承关系,多重继承下对象的存取效率是否会受影响?我们来看一个具体的例子:

#include <cstdio>

class Point {
public:
    int x;
};
class Point2d {
public:
    int y;
};
class Point3d: public Point, public Point2d {
public:
    int z;
};

int main() {
    printf("&Point2d::y = %d\n", &Point2d::y);
    printf("&Point3d::x = %d\n", &Point3d::x);
    printf("&Point3d::y = %d\n", &Point3d::y);
    printf("&Point3d::z = %d\n", &Point3d::z);
    Point3d p3d;
    p3d.x = p3d.y = p3d.z = 1;
    Point2d* p2d = &p3d;
    p2d->y = 2;

    return 0;
}

输出结果是:

&Point2d::y = 0
&Point3d::x = 0
&Point3d::y = 0
&Point3d::z = 8

第1、2行输出是
0
很正常,因为对于
Point2d
类来说只有一个成员
y
,也没有继承其他类,所以
y
的偏移值是
0
,第2行输出的是
x
的偏移值,它从
Point
类继承而来,排在最前面,所以偏移值也是
0
。但为什么第3行输出也是
0
?难道不应该是
4
吗?从第4行的输出看到z的偏移值是
8
,说明前面确实有两个成员在那里了。其实这里应该是编译器做了调整了,因为
Point2d
是第二基类,访问第二基类及之后的类时需要调整
this
指针,也就是将
Point3d
对象的起始地址调整为
Point2d
的起始地址,一般是将
Point3d
的地址加上前面子类的大小,如
&p3d+sizeof(Point)
。来看看上面代码生成的汇编代码:

main:                           # @main
    # 略...
    lea     rdi, [rip + .L.str]
    xor     eax, eax
    mov     esi, eax
    mov     al, 0
    call    printf@PLT
    lea     rdi, [rip + .L.str.1]
    xor     eax, eax
    mov     esi, eax
    mov     al, 0
    call    printf@PLT
    lea     rdi, [rip + .L.str.2]
    xor     eax, eax
    mov     esi, eax
    mov     al, 0
    call    printf@PLT
    lea     rdi, [rip + .L.str.3]
    mov     esi, 8
    mov     al, 0
    call    printf@PLT
    mov     dword ptr [rbp - 8], 1
    mov     dword ptr [rbp - 12], 1
    mov     dword ptr [rbp - 16], 1
    xor     eax, eax
    lea     rcx, [rbp - 16]
    cmp     rcx, 0
    mov     qword ptr [rbp - 32], rax       # 8-byte Spill
    je      .LBB0_2
    lea     rax, [rbp - 16]
    add     rax, 4
    mov     qword ptr [rbp - 32], rax       # 8-byte Spill
.LBB0_2:
    mov     rax, qword ptr [rbp - 32]       # 8-byte Reload
    mov     qword ptr [rbp - 24], rax
    mov     rax, qword ptr [rbp - 24]
    mov     dword ptr [rax], 2
    # 略...
    ret
# 略...

上面汇编代码中的第3到第7行对应的是上面C++代码的第一条
printf
打印语句(C++代码第17行),这里可以看到给
printf
函数传递了两个参数,分别通过
rdi
寄存器和esi寄存器,
rdi
寄存器保存的是第一个参数字符串,它的地址是
[rip + .L.str]

.L.str
是字符串存储在数据段中的位置标签,
rip
+这个标签可以取得它的偏移地址,以下的
.L.str.1、.L.str.2

.L.str.3
都是字符串的位置标签),
esi
是第二个参数,这里的值被设为
0
了。

第8到12行汇编代码对应的是C++代码中的第二条
printf
打印语句,同样地,给
rdi
寄存器设置字符串的地址,给
esi
寄存器设置值为
0
。第13到第17行对应的是第三条
printf
打印语句,第18到第21行就是对应C++代码中的第四条
printf
打印语句,可以看到编译器在编译期间已经确定好了它们的偏移值为
0, 0, 0, 8

第22到24行对应的C++的第22行代码,是对对象的成员进行赋值,可以看到通过对象来存取数据成员跟独立的类存取数据成员是一样的,已经知道了每个成员的内存地址了,所以存取的效率跟独立的类的存取效率没有差别。

汇编代码的第25行到37行对应C++的第23、24行代码,是将Point3d的地址转换成父类
Point2d
的指针类型,通过父类
Point2d
的指针来访问数据成员。前面提到过的将子类转换成第2及之后的基类时会进行
this
指针的调整,这里就是具体的实现。相当于伪代码:
Point2d* p2d = &p3d+sizeof(Point)
,其实这里应该还需要判断下
p3d
是否为
0
,所以正确应该是:
Point2d* p2d = &p3d ? &p3d+sizeof(Point) : 0
。上面的第26到29行即是判断是否为
0
,如果为
0
则跳转到第33行,如果不为
0
则将
p3d
的地址
[rbp - 16]
加上
4

4

Point
类的大小,然后存放在
[rbp - 32]
,再加载到
rax
寄存器中,然后对其赋值
2
(汇编代码第37行)。

通过分析汇编代码,多重继承的情况,如果是通过对象来存取数据成员,是跟独立类的存取效率是一致的,如果是通过第二及之后的基类的指针来存取,则需要调整
this
指针,可以看到对应的汇编代码也多了好好多行,所以效率上会有一些损失。

如果您感兴趣这方面的内容,请在微信上搜索公众号iShare爱分享并关注,以便在内容更新时直接向您推送。
image

这是作者大学时期在家里遇到的一个非常奇怪的网络问题,作者的父亲是一名经验丰富的网络工程师,他们家里使用了一个复杂的网络设置,通过 Wi-Fi 桥接的方式,将父亲公司的高速商业网络连接到家中。但是有一天,作者发现家里的 Wi-Fi 只有在下雨时才能正常工作。。。

事情发生在十多年前,那时我还是个愣头青大学生。有一次放暑假,我兴冲冲赶回家准备和父母共度几周美好时光。毕竟一年没回来了,而且学校在大海的另一边,回来一趟不容易。

我的父亲是一名工程师,在我出生之前就已经开始潜心研究网络设备。他创办的公司为全国各地的机构设计和部署了各种复杂的网络系统——从办公楼的千兆以太网,到城际间的
视距
微波链路,无所不包。

我刚回到家,父亲就一本正经地告诉我:
Wi-Fi 只有下雨天才能用

“什么?” 我惊讶地长大了嘴巴。

“Wi-Fi 只有在下雨的时候才能用,” 他耐心地重复道,“几周前开始的,我还没来得及查。”

“这不可能,” 我说。
常识告诉我,
雨只会让无线信号质量变差
,而不是变好

。怎么可能因雨而好转呢!

我要在家住两周啊!两个星期都不能上网?这绝对不可以!

否认现实

随即我打开笔记本电脑,开始检查网络。

结果发现 Ping 任何网站都有高达 98% 的丢包率。网页根本打不开!

这可咋整呢,
几个月前我刚和大洋彼岸的一个妹子谈着异地恋,她现在在地球的另一端,我总不能跟人家说我不能跟你打电话是因为家里没下雨?
这也太特么扯淡了吧。。。

由 AI 创作

那时候移动数据的价格极其昂贵,昂贵到我家里的手机都不敢开通数据套餐。当时是没法法像现在这样直接用手机的数据套餐来解决问题的。

我在房间里烦躁地踱来踱去,急得像热锅上的蚂蚁。

就在此时,窗外突然下起了雨。

求雨心切

雨水落下不到 5 分钟,奇迹出现了——丢包率降到了 0%!

我简直不敢相信自己的眼睛!生怕连接随时会中断,我打开浏览器,迅速打开了无数个标签页……

雨下了一个小时,家里的网也稳如老狗一个小时。

然而好景不长,
雨停约 15 分钟后,丢包率又飙升到 90% 以上
。网络再次陷入瘫痪,就像魔法突然失效了一样。

我现在只想天天下雨,老天啊,只要你能下雨,让我干啥都行。我开始密切关注天气预报,祈祷未来几天能有下更多的雨。

万幸的是接下来几天都是阴天,不停地上演这样的戏码:

雨一开始下,网络连接就在几分钟内恢复畅通。

雨一停,网络连接就在 15 分钟内再次瘫痪。

尽管内心有多么不愿承认,但铁证如山——Wi-Fi 确实像是被施了魔法,只有下雨时才能正常工作!

此时此刻,我面临一个抉择:

我是该继续消沉下去,闷闷不乐,相信玄学,根据天气预报来安排和女友的约会。

还是该挣脱消极情绪的桎梏,以工程师的理性思维,去追寻问题的真相?


去他妈的玄学!老子可是个讲逻辑和理性的工程师!
” 我暗暗给自己打气。

决心已下。我要解开这个谜团,我绝不会坐以待毙。

定位问题

在展开调查之前,先给大家介绍一下我家的网络状况。

由于我父亲在网络解决方案领域经验丰富,所以他在家中也搞了一套顶级的网络设置——过去 10 年来,它一直完美运行,从未出过问题!

父亲办公室的网络是商业级的,速度很快。而家里的网络则没有这么好的选择!但我们家向来不愿将就,除非别无选择。

好在父亲的办公室和我们的公寓只隔着几个街区的一个小山,而我们二楼的公寓则占据了街区的制高点。亲想方设法在办公室和公寓之间架设了一条视距 Wi-Fi 桥接——
两端各有一个高增益定向 Wi-Fi 天线,相互紧紧对准
。如此一来,我们在家里也能分享到高速的商业级网络体验!

由 AI 创作

我开始排查网络,看看连接到底是在哪里中断的。

家中的 Wi-Fi 路由器一切正常——0% 丢包率。Wi-Fi 桥接的本地端也毫无问题。

但当我 Ping Wi-Fi 桥接的远端时,丢包率却高达 90% 以上——连接其后的任何网络设备,Ping 测试的丢包率也一样。
问题的根源显然在于 Wi-Fi 桥接!

但是问题究竟出在哪里?为什么偏偏在这个时候出现,要知道这套系统已经完美运行了将近 10 年,不管外面是刮风还是下雨!

一场雨怎么可能奇迹般地修复一个 Wi-Fi 桥接,这到底是怎么一回事?

重重谜团等着我去揭开。是时候去寻找答案了!

刨根问底

作为一名经验丰富的工程师,
我率先尝试了最简单最牛逼的方法——关机重启
。但很遗憾,这招现在不灵了。

于是我开始逐一排查网络中的设备:

  • 会不会是某个设备日久失修,年久失修?我用笔记本连上每台设备的本地以太网接口,运行诊断程序,做 ping 测试,结果一切正常。
  • 会不会是哪根网线松脱或脱落?我仔细检查了每根电缆,发现它们都连接得很牢固。
  • 会不会是电源适配器老化失效?我测试了每个电源模块,电压输出都很稳定。
  • 会不会是自动固件更新意外中断,导致系统崩溃?我核对了每台设备的固件版本,确认都已是最新版本。
  • 会不会是户外天线接头经历了风吹日晒,已经锈迹斑斑?我爬上梯子仔细检查,发现接头完好如初。

不得不承认,相比软件调试,硬件排查实在是个苦差事!
我就像个不怕死的猴子一样,上房揭瓦,爬梯子,满小区乱窜,检查从调制解调器到网线再到天线的每一个组件。

来来回回折腾了 N 次之后,我突然意识到,这些年小区里发生了多大的变化。

上大学之前,我曾在一所寄宿高中度过了四年的时光。我还加入了国家数学和信息学竞赛队,征战
IMO

IOI
,即便是暑假,大部分时间也都耗在了集训营和赛场上。如今再看,不少小区的店铺已然换了新主,不少房屋也都焕然一新,
就连那些昔日的小树苗,也已然长成参天大树,郁郁葱葱

就在此时,我恍然大悟!

发现真相

我飞奔回家,三步并作两步爬上 Wi-Fi 桥接器支架,紧紧攀附栏杆,悬在两层楼高的半空中,摇摇欲坠。

现在回想起来,当时应该系上安全带才对……不过为了网络,这点代价算得了什么!更何况,
还有个异地恋女友在线等着我呢
,我可不是为了看 Netflix 或刷 Twitter 才这么拼命的。

我眺望山脚下,搜寻 Wi-Fi 桥接器另一端的天线。

不,准确地说,是朝天线理应在的方向望去,却看不见它的身影!
我的视线被邻居家一棵参天大树遮挡,最顶端的枝桠正巧在两个天线之间悠然地摇曳着

问题的根源昭然若揭!

问题剖析与解决方案

事情的来龙去脉是这样的。

多年前,我们安装了这套 Wi-Fi 桥接器。一开始,一切运转正常,堪称完美。

但随着时间的推移,邻居家的那棵树越长越高。就在我暑假回家前不久,它顶端的新枝嫩叶终于长到足以干扰 Wi-Fi 信号的高度。

不过话说回来,它也只是勉强能够到而已,千钧一发。

每当下雨时,雨水便会在树叶枝桠间汇集,沉甸甸地把它们压弯,恰好避开了 Wi-Fi 信号的必经之路。

有趣的是,
即便不在天线的视线范围内,天线旁边的物体也会对信号造成干扰
。要想获得最佳信号质量,天线之间的
菲涅耳区
最好是一马平川,畅通无阻。当然实际中很难做到尽善尽美,所以像 Wi-Fi 这样的无线设备通常会采用纠错编码等技术,使其在菲涅耳区略有遮挡的情况下,依然能保持稳定工作。

一旦雨过天晴,雨珠便会持续从树梢滚落。大约十五分钟后,树冠渐轻,慢慢舒展开来,又不偏不倚地探进了我们的数据通道。于是,Wi-Fi 就开始 “罢工” 了。

问题的解决方案其实很简单:设备升级呗!我们把
老旧的 802.11g 设备
换成了
崭新的 802.11n 设备
。新设备运用了一些前沿的数学和物理学原理,令信号的抗干扰能力大幅提升。

比如 802.11n 引入了一项新技术,名为 “
波束成形
”——通过巧妙调制多个相同频率的发射天线,在扩大覆盖范围的同时,还能优化信号品质。现代 Wi-Fi 设备虽然天线单元不多,却已能通过波束成形技术,灵活调制信号的传播方向。如果我们进一步增加天线数量,便可打造一个更为强大的
相控阵天线
。你有没有想过,为什么
Starlink 天线是平的
,而
不像旧的卫星电视天线那样是个大 “碟子”
?奥妙就在于相控阵技术的运用。通过灵活调制每个天线单元的相位,它无需任何机械转动,就能实时锁定天际掠过的
Starlink 卫星群

这是魔法吗?不!
这是物理的力量!

几天后,新设备到了,我迫不及待地爬上脚手架,安装好新天线。

我将几颗螺丝拧紧,理顺连接线,然后 Wi-Fi 的 “已建立连接” 指示灯再次亮起了熟悉的绿光。

而这一次,
晴空万里,阳光灿烂

家里的网络重焕生机,皆大欢喜。



原文链接

一、日常问题

1)页面卡死

QA 将一个页面放在客户端中访问,进行功能测试的时候,发现运行在 iOS 16.7 系统中时,网页会卡死。

将页面地址复制到 Safari 浏览器中,访问也会卡死。而据 QA 描述,测试环境没有问题,其他 iOS 版本的系统以及安卓手机也没有问题。

有问题的手机,其测试和线上的客户端版本都是相同的,初步断定和 iOS 系统有关。

摇上 iOS 的人,让他们帮忙调试,发现卡在接口通信,接口通信中的响应一直在 loading,没有显示。

让客户端排查,他们什么头绪也没有,说可能页面渲染被阻塞了。

将接口换个路径,响应内容相同,但依然会被卡死。以为是接口请求时机的问题,那就加个延迟,依然有问题。

将接口响应置空,这时,页面不会卡死,看来与响应内容有关。

接下来将响应内容中的数组只返回一个,页面仍然不会卡死,尝试二分查找的思路,再返回 10 个,依旧正常,返回 15 个,出现卡死。

那看来问题出在这 5 条记录中,一个一个排查,发现一个 emoji 的蘑菇表情会让页面异常,过滤后就正常了。

又去摇客户端的人,他们排查半天也没发现问题,因为不是所有的 emoji 表情都异常,目前就一个,也找不出规律。

此时,一名客户端的人说用相同系统的手机访问管理后台,可以正常显示这个表情,活动页面和后台的区别就是用了不同的框架。

前者是 React 后者是 Vue,既然如此,那就查一下这个表情在 Vue 中输出的写法。

使用的是模板语法,将其替换成 v-html 的写法后,一切恢复了正常,特地查了这个属性的原理。

它会将变量内容赋给元素的 innerHTML 属性,而模板语法会经过很多复杂步骤,可能是某一步操作发生了死循环导致了卡死。

遇到此类难以马上解决的问题,需要抽丝剥茧的分析自己获取到的线索,然后一步一步推导,最后得出结论,这有点像探案推理。

总结上述这套过程就是寻线索,找差异,缩范围,给结论,如此递归反复,直到解决问题为止。

2)协作问题

我们组的重点是活动和管理后台,版本涉及的都是比较边缘的需求,有时候都没有直接需求。

最近就遇到,QA 在测版本需求时,临时找我们修改 BUG,这让我们很被动,安排的计划也被打乱了。

剖析了下这个问题,发现来源于两个方面。第一是产品在设计文档时,缺失了我们要修改的那部分业务。

但是人总归有盲区,漏掉点需求很正常。第二是技术评审不够详细,现在的评审流于形式,其他组我也控制不了。

目前就是比较粗的过,然后经常在开发时,遇到问题再和产品测试等人讨论,这个流程目前破不了。

想来想去,想到了 QA,是他们告诉我们有 BUG,那只需要求他们在设计测试用例时,涉及到我们这块的,就提前通知。

即使在版本中,我们没有要修改的需求,也要同步给我们,这样就不至于在测试过程中那么被动,被赶着改 BUG。

最近在做一个 Electron 客户端的项目,也遇到了两个协作问题。第一个就是协作团队的文档缺失,让我们吃了不少苦头。

我们的任务是将客户端的一个功能,搬到 Electron 项目中,但是在开发过程中,没有技术文档,遇到问题只能口头讨论,很费时间。

第二个就是第三方服务文档缺失,虽然他们会提供示例,但是每次都要过一点时间才能给到,无法第一时间拿到。

这就会延缓我们的开发进度,很多时候只能干等。总结下来,就是不同团队的技术文档缺失影响了我们的开发。

这也提醒我们自己组,技术文档要补充完善,以免影响他人。

二、工作优化

1)缺失的性能测试

最近上了一个大活动,其中会调用服务端的一个内部接口,这个接口是这次活动新写的。

刚上线的时候,发现了少量的慢响应,当时并没在意。正好赶上双休日,这两天慢响应一下子就上升了。

周末在家排查,发现是服务端的那个新接口问题,但想到页面还能打开,只是响应慢点,也就没有组织人力去修复了。

毕竟周末时间找人也比较困难,就这么拖到周一,他埋了日志,周二才做了一版修复,上线。

结果周三零点,整个活动页面的接口都超时,被电话打醒,赶紧排查,马上摇人服务端。

他说有个服务一直在告警,那你们居然没点反应,我真是醉了,运维我也摇了,过了半小时才来。

他说那个服务的 CPU 一直居高不下,服务端也没找出问题,还是我点醒他重启下服务,才恢复,额。

这么一折腾,运营都要跳起来了,在群里狂抱怨,我也很无奈。

其实我这边的日志显示从周二的 21 点开始,慢响应持续上升,我也大意了。

改了接口居然没关注,以为一切顺利,其实不然,今晚的日志一定要仔细关注了。

服务器的配置也会让运维升级,其实这个接口也是让 QA 测试过的,并没有功能问题。

但是却存在严重的性能问题,QA 之前自己也说以后得增加这块,这次的问题是三方都有责任,既有客观也有主观原因。

公司在经历裁员之后,服务端有经验的员工都离职了,这个留下的小伙来了才 1 年,模块也刚接手,开发经验也缺乏。

以后得完善测试流程,以及自己要多留心眼,就好像常说的不要完全信任用户的输入,协作时也有这点意思。

2)OKR

公司要求我们每个双月要制订自己的 OKR,然后还要对齐上级的 OKR。

我对齐了上级的双月高等级业务需求,不过我总觉得这个并不算 OKR,因为这些都是我们本月排进日程的任务。

第二个 O 是质量相关,将线上因代码问题导致的高等级 BUG 或事故降低至 0,之前写的 KR 不够具体,无法明确 KR 与 O 之间的联系。

那么这次又修改了一下,分析以往发生重大线上事故的原因,发现很多时候是因为慢 SQL 和数据库表太大撑满了整个数据库的容量。

由此就马上将其作为一个 KR,组织排查与优化,保持数据库健康。还真找到不少潜在问题,例如已没有记录的一张表,还占用 29.12GB 容量,立刻释放掉。

然后通过下面的 SQL 语句找到排名靠前的数据库表,清理不再需要的数据库表。

select       table_schemaas '数据库',
       table_name
as '表名',
       table_rows
as '记录数',
      
truncate(data_length/1024/1024, 2) as '数据容量(MB)',
      
truncate(index_length/1024/1024, 2) as '索引容量(MB)' frominformation_schema.tableswhere table_schema='backend' order by data_length desc, index_length desc;

另一个在使用的数据库是 MongoDB,用代码列出容量占用比较高的集合,发现可以释放掉 100G,每个月能减少 50 元的费用。

第二个 KR 是定期 Code Review,包含细节、安全、边界等内容,保障代码健壮性。Code Review 在执行时,安全性往往会被忽略,仅仅是复查逻辑的合理性是不够的。

还有些细节也应该放上台面,隔壁组服务端前段时间出现了非常严重的线上紧急 BUG,发生在 2 月 29 日那天,他们有段计算年龄的代码。

会将当天推算到 18 年前的当天,但是 18 年前的 2 月没有 29 号,导致计算异常发生了 BUG。但其实算年龄,完全不必如此计算,若在 Code Review 中就将此问题抛出。

那么就不会发生此类线上问题,关键发生在凌晨,都很难及时修正。

对三个 KR 分析飞书告警,修复会对业务造成影响的潜在问题。我们会将线上的页面错误和接口错误实时通过飞书发出告警。

这些告警可能不是最为紧急严重的,可能就是偶尔发生的,但是将它们修复,有助于避免潜在的风险。

热点随笔:

·
博客园商业化之路-开篇:开源的脚步,商业化的出路
(
博客园团队
)
·
经过腾讯云这波故障,我想表扬的点和学到的职场保命法则。
(
why技术
)
·
一周涨 15k Star 的开源项目「GitHub 热点速览」
(
削微寒
)
·
面试官:为什么忘记密码要重置而不是告诉你原密码?
(
JavaGuide
)
·
.NET开源强大、易于使用的缓存框架 - FusionCache
(
追逐时光者
)
·
C#开发的PhotoNet看图软件 - 开源研究系列文章 - 个人小作品
(
lzhdim
)
·
争论不休的一个话题:金额到底是用Long还是BigDecimal?
(
码农Academy
)
·
他来了他来了,.net开源智能家居之苹果HomeKit的c#原生sdk【Homekit.Net】1.0.0发布,快来打造你的私人智能家居吧
(
三合视角
)
·
淘宝二面:MySQL里有2000万条数据,但是Redis中只存20万的数据,如何保证redis中的数据都是热点数据?
(
码农Academy
)
·
我用这10招,能减少了80%的BUG
(
苏三说技术
)
·
WPF/C#实现图像滤镜优化方案:打造炫目视觉体验!
(
庆喜
)
·
C#判断窗体是否被遮挡 - 开源研究系列文章
(
lzhdim
)

热点新闻:

·
苦自动续费久矣,小米正式出手!这种APP乱象终于要凉了
·
重磅!Meta推出开源大模型Llama 3:最大底牌4000亿参数,性能直逼GPT-4
·
百度的大模型学费
·
波士顿动力Atlas,再见!退役视频引数十万观众泪目,液压退出历史舞台
·
英伟达18年员工分走6200万美元巨额股票,原地退休!
·
极越回应员工买小米汽车被开除:上班时间为竞品做宣发
·
液体都可编程了?哈佛新型超材料登Nature,粘度透明度弹性可变
·
波士顿动力发布全电动Atlas机器人,外型更像人但灵活度远超
·
「纯血」鸿蒙真机界面曝光
·
特斯拉裁员前,马斯克为这事暴怒,开除了项目负责人
·
马斯克,会如何让盲人重见「光明」?
·
余承东谈“车载手机支架”:“美国品牌”车主称车机导航不好用