2024年1月

公众号「架构成长指南」,专注于生产实践、云原生、分布式系统、大数据技术分享

试想一下,如果有一个项目有50 个微服务,每个微服务都有service、deployment、ingress、pvc等 yaml 文件,算下来大概有 200 个文件,然后这个项目需要基于k8s进行私有化交付,如果是你会怎么快速部署应用?

首先让我们先思考一下

  • 200 个文件是否通过
    kubectl apply
    进行部署,写个shell 脚本for 循环一个个读取执行?,但是如果这些yaml 文件更新了,如何同步?
  • 如果这些pod进行需要升级怎么办,一个个修改镜像 tag?
  • yaml文件关联地址如何更新,比如数据库地址、文件系统地址,日志目录,资源大小等?
  • 如果想进行卸载,如何做,一个个去删除?

如果有一个工具能把这些yaml文件放在一个包里,类似
npm

maven
这样的包管理工具,然后把关键的参数暴露出来,在部署时指定这些参数,执行一键部署,在卸载时一键卸载是不是很方便,而Helm 就是这样的工具

什么是Helm

Helm 是一个能够在 Kubernetes 上打包、部署和管理应用程序的工具,即使是最复杂的 Kubernetes 应用程序它都可以帮助定义,安装和升级,同时Helm 也是
CNCF
的毕业项目。

以下Helm中的概念

Helm Charts
:预先配置yaml的模板,在这里叫Chart,用于描述 Kubernetes 应用程序的yaml和配置

Helm Client
:用于与 Helm 交互并管理这些Chart版本的命令行界面

Chart 仓库
:管理Chart的仓库,跟Maven的
Nexus
一个意思,比如在公司环境构建上传,在客户的机房连接到这Chart 仓库下载Chart,并部署到k8s中。

我们要做什么?

这里我们不介绍Helm 的具体函数,具体可以查官网,这里只讲一下如何使用Helm,让你对Helm有一个认识,知道在什么场景下使用即可,所以我们结合一个示例讲一下玩法。

示例介绍

有一个运维部署系统,分为前后端,需要部署到客户机房,在k8s架构如下,可以看到需要8 个文件(其中Istio需要2个yaml文件),下面通过示例一步步创建这个Chart

示例创建

我们通过
helm create dp-manage
命令创建一个Chart,执行完以后,默认会生成一个 nginx 的Chart,如下图

关键文件说明
Chart.yaml

定义了当前 chart版本,以及描述当前chart用途,其中 name 参数表示 chart 名称,后期上传下载都会用此名称

values.yaml

可变参数,都是在此文件中定义,在yaml模板中引用,比如:
image.repository
,而引用则通过
.Values
+变量的名进行引用,如下图

_helpers.tpl

通用代码块定义,类似于freemaker的宏,然后在yaml中通过名称进行引用,
include
修饰的都是,比如
dp-manager.selectorLabels

示例修改

由于默认创建一个Nginx 的 Chart 无法满足我们需求,所以删除掉多余的文件,并添加运维部署系统的yaml 文件,如下图

可以看到有8个模板文件,其中backend-dp.yaml定义如下

通过引用
values.yml
参数和
_helpers.tpl
的通用代码块,最终会生成一个k8s的deployment.yml文件,其他文件就不一一介绍

构建与部署

通过前面的介绍,需要把Chart构建好以后,上传到Chart仓库,然后在客户机房连接这个仓库,进行下载,因此需要一个Chart 仓库,这里我们使用Harbor ,Habor可以作为容器镜像仓库也可以作为 Chart仓库一举两得,推荐使用。

关联仓库

在安装好 Helm client 以后,在本地执行以下命令,关联到仓库,

helm repo add myrepo https://host/仓库地址 \
--username sre \
--password xxxx

添加以后,执行
helm repo list
可以查到添加的
myrepo
仓库

构建打包

仓库关联好以后,执行
helm package dp-manager
命令打包,会生成一个
dp-manager-1.0.0.tgz

执行
helm cm-push dp-manager-1.0.0.tgz myrepo
推送至Harbor 仓库,myrepo为我们的仓库名称,推送完以后登录Harbor 查看,如下图

拉取部署

Chart 上传以后,我们在客户机房安装Helm Client和Kubectl客户端,具体安装方法查询网上,通过
helm repo add
关联Habor仓库地址,关联以后执行search 命令查找运维部署系统的Chart

helm search repo  myrepo/dp-manager

Chart查询到以后,就可以进行部署了,部署分为两种方式,一种是下载Chart至本地,修改values.yaml文件在部署,另外一种是不用下载直接指定参数部署,我们使用第二种

helm install dp-manager myrepo/dp-manager  \
--set istioGateway.hosts={dpmanager.test.com} \
--set pv.log.pvEnabled=false \
--set pv.log.type=storageClass \
--set pv.log.pvc.storageName=gfs-storage \
--set pv.log.pvc.storage=20Gi \
--set apollo.cluster=default 

我们在这个命令中指定了域名,存储大小等参数,这些参数通过values.yaml文件暴露,另外此命令部署运维部署系统至default命令空间,可以通过
-n
参数指定命名空间

命令执行完以后,可以通过
helm list
进行查看,如下图

helm list

更新

假设apollo.cluster值由 default修改为prod,执行
upgrade
更新即可

helm upgrade dp-manager myrepo/dp-manager  \
--set istioGateway.hosts={dpmanager.test.com} \
--set pv.log.pvEnabled=false \
--set pv.log.type=storageClass \
--set pv.log.pvc.storageName=gfs-storage \
--set pv.log.pvc.storage=20Gi \
--set apollo.cluster=prod 
卸载

卸载非常简单,执行以下命令即可

helm uninsall dp-manager

如果本篇文章对您有所帮助,麻烦帮忙一键三连(
点赞、转发、收藏
)~

扫描下面的二维码关注我们的微信公众帐号,在微信公众帐号中回复◉加群◉即可加入到我们的技术讨论群里面共同学习。

基于ChatGLM-6B第一版,要注意还有ChatGLM2-6B以及ChatGLM3-6B

转载请备注出处:
https://www.cnblogs.com/zhiyong-ITNote/

PrefixEncoder

作用:在微调时(以P-Tuning V2为例),方法训练时冻结模型的全部参数,只激活PrefixEncoder的参数。
其源码如下,整体来看是比较简单的。

class PrefixEncoder(torch.nn.Module):
    def __init__(self, config):
        super().__init__()
        self.prefix_projection = config.prefix_projection
        if self.prefix_projection:
            # 使用一个两层(线性层)的MLP编码prefix
            self.embedding = torch.nn.Embedding(config.pre_seq_len, config.hidden_size)
            self.trans = torch.nn.Sequential(
                torch.nn.Linear(config.hidden_size, config.hidden_size),
                torch.nn.Tanh(),
                torch.nn.Linear(config.hidden_size, config.num_layers * config.hidden_size * 2)
            )
        else:
            self.embedding = torch.nn.Embedding(config.pre_seq_len, config.num_layers * config.hidden_size * 2)

    def forward(self, prefix: torch.Tensor):
        if self.prefix_projection:
            prefix_tokens = self.embedding(prefix)
            past_key_values = self.trans(prefix_tokens)
        else:
            past_key_values = self.embedding(prefix)
        return past_key_values

为什么源码注释中会说到MLP?定位追溯:

self.mlp = GLU(
    hidden_size,
    inner_hidden_size=inner_hidden_size,
    bias=use_bias,
    layer_id=layer_id,
    params_dtype=params_dtype,
    empty_init=empty_init
)

def default_init(cls, *args, **kwargs):
    return cls(*args, **kwargs)

class GLU(torch.nn.Module):
    def __init__(self, hidden_size, inner_hidden_size=None,
                 layer_id=None, bias=True, activation_func=gelu, params_dtype=torch.float, empty_init=True):
        super(GLU, self).__init__()
        if empty_init:
            init_method = skip_init
        else:
            init_method = default_init
        self.layer_id = layer_id
        self.activation_func = activation_func

        # Project to 4h.
        self.hidden_size = hidden_size
        if inner_hidden_size is None:
            inner_hidden_size = 4 * hidden_size
        self.inner_hidden_size = inner_hidden_size
        self.dense_h_to_4h = init_method(
            torch.nn.Linear,
            self.hidden_size,
            self.inner_hidden_size,
            bias=bias,
            dtype=params_dtype,
        )
        # Project back to h.
        self.dense_4h_to_h = init_method(
            torch.nn.Linear,
            self.inner_hidden_size,
            self.hidden_size,
            bias=bias,
            dtype=params_dtype,
        )

    def forward(self, hidden_states):
        """
        hidden_states: [seq_len, batch, hidden_size]
        """

        # [seq_len, batch, inner_hidden_size]
        intermediate_parallel = self.dense_h_to_4h(hidden_states)

        intermediate_parallel = self.activation_func(intermediate_parallel)

        output = self.dense_4h_to_h(intermediate_parallel)

        return output

# 转载请备注出处:https://www.cnblogs.com/zhiyong-ITNote/

init_method
对应到
default_init
,这个函数的作用与直接调用类构造函数相同,但它提供了一种更灵活的方式来创建类的实例,因为它可以接受任意数量的位置参数和关键字参数。在Pytorch中,用于模块化的构造函数。从源码分析来看,GLU/MLP类就是构造了两个线性层与gelu激活函数,其结构可简化如下:


PrefixEncoder
类的初始化方法来看,其就是embedding层与MLP的组合。其结构可简化如下:

详细解读可参考
ChatGLM的模型架构

Q:在这里还有一个问题,从哪里可以定位溯源到微调时禁用了全部的参数,只激活PrefixEncoder的参数并调用了该类?

转载请备注出处:
https://www.cnblogs.com/zhiyong-ITNote/

激活函数与位置编码

代码简单明了,RoPE的理论知识可以多了解。

attention_fn

伪代码表示为:

def attention_fn(
        self,
        query_layer,
        key_layer,
        value_layer,
        attention_mask,
        hidden_size_per_partition,
        layer_id,
        layer_past=None,
        scaling_attention_score=True,
        use_cache=False,
):
    xxxx

标准的注意力机制计算公式如下:




多头注意力就是将多个单头注意力的结果拼接起来,再点乘一个新的权重参数。


attention_fn
函数实现了注意力的核心计算过程(即上述数学表达式),包括计算注意力分数、注意力概率和上下文层。这些计算对于实现许多自然语言处理任务,如语言建模、命名实体识别等,都是非常重要的。

SelfAttention

伪代码表示为:

class SelfAttention(torch.nn.Module):
    xxxx

attention_mask_func
将注意力掩码应用于Transformer模型中的注意力得分中。

@staticmethod
def attention_mask_func(attention_scores, attention_mask):
    attention_scores.masked_fill_(attention_mask, -10000.0)
    return attention_scores

apply_rotary_pos_emb_index
函数为

注入了RoPE位置信息,然后调用
attention_fn
计算注意力概率、上下文层表示,并得到返回值。这些都是在
forward
函数中调用处理的。
image.png
最后还调用了dense对上下文表示做线性计算,返回输出。

转载请备注出处:
https://www.cnblogs.com/zhiyong-ITNote/

GLU

GLU也可以理解为是MLP,在后面版本的ChatGLM中,去掉了GLU类的定义声明,直接换成了MLP。在上面已经写过不再赘述。

GLMBlock

一般都会把
GLMBlock
对应为transformer结构的实现。从其构造函数来看,主要是拼接各个层到一起。

从代码来看,中间有两次的残差连接,如下所示

# Residual connection.
alpha = (2 * self.num_layers) ** 0.5
hidden_states = attention_input * alpha + attention_output

mlp_input = self.post_attention_layernorm(hidden_states)

# MLP.
mlp_output = self.mlp(mlp_input)

# Second residual connection.
output = mlp_input * alpha + mlp_output

转载请备注出处:
https://www.cnblogs.com/zhiyong-ITNote/

日拱一卒,功不唐捐。

在了解 @Conditional 之前先花 10 秒钟复习一下 @Configuration 这个注解。

@Configuration 是干什么?

是配合 @Bean 注解来配置 Spring 容器的 bean 的。

那它为什么会出现呢?

因为配置 bean 的另一种方式是 xml ,狗都不用。

那给个示例看看呗?

简单。

@Configuration
public class AppConfig {
    @Bean
    public MyBean myBean() {
        // 初始化, 配置, 返回bean...
    }
}

下面进入主题:

@Conditional 是什么东西?

首先明确第一点:

@Conditional 是
Spring
提供的。

其次明确第二点:

Conditional 中文译为:
有条件的,依...而定的。

前边既然讲了 @Configuration 的简单用法,那么问题就是:

@Configuration + @Conditional 有没有搞头?

有搞头。

怎么搞?

那就是可以给@Configuration加上一个开关。

我打开开关,你这配置就好用。

我关闭开关,你这配置就不好用。

进一步升级。

我给 @Configuration 里的 @Bean 加一个开关。

我打开开关,你这 bean 就注册。

我关闭开关,你这 bean 就不注册。

那开关在哪里呢?

废话不多说,先上代码。

@Configuration
@Conditional(MyCondition.class)
public class ConditionConfig {
    @Bean
    public ConditionBean conditionBean() {
        return new ConditionBean();
    }
}

开关就是代码中的
MyCondition.class

WTF is MyCondition.class ?

MyCondition类就是我们自定义的开关,我们定义什么时候开,什么时候关的逻辑。

很明显这个逻辑不可能是 Spring 给的。

但 Spring 又必须插手管理。

通过什么呢?

没错,通过
接口 Condition

这个接口中什么都没有,只有一个
matches
方法,返回一个 boolean 值。

显而易见,你返回 true, 开关打开,返回 false,开关关闭。

MyCondition 类代码如下:

    public class MyCondition implements Condition {
        @Override
        public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
            return true;
        }
    }

可以看到 matches 方法中的两个参数,可以简单介绍一下。

ConditionContext context
这个参数提供了一种方法来访问关于Spring容器的信息。

AnnotatedTypeMetadata metadata
这个参数提供了对被注解类的元数据的访问。

基于这两个参数我们就可以
创建复杂和动态的条件来控制开关的闭合

例如,你可以基于环境变量、定义的bean、类的存在等因素,来决定是否创建某个bean。

除了自定义 Condition,Springboot 还为我们扩展了一些常用的 Condition。


2023年转眼间就这样过掉了,作为一名资深C#的开发员人员,年龄也大了1岁,从最早接触C#开始,算下来已经超过15年以上了,随着工作经验的不断增加,物价不断的飞涨以及家庭支出的不断上涨,工作1份工资已经不能满足这个飞涨的时代,所以在好多年前就利用业余时间开始了接软件研发的私活,从刚开始的最普通的软件研发界面,不断重复,到研发一个桌面型软件权限管理框架,软件开发有代码生成器,做一个简单的企业管理软件其实并不难,而且还是很快的能做出来,人家说你这么多年软件开发经验,怎么没有转到项目管理岗位上去,怎么说呢,其实期间是有机会转到项目管理的,只是不想放弃软件的代码开发(其实是有点心慌,担心万一项目经理做不好,连后路都没了),其实也是基于对软件开发的热爱,期间做过,程序员、高级程序员、程序开发经理、开发组长的职务,你说让我来带一个项目行不行,那肯定行,因为接做外面的项目,都是1个人从软件需求到软件的交付都是自己去实现的,基本上即是项目经理又是开发设计人员,做好的软件有ERP管理、进销存管理、库存管理等企业管理软件,基本上给企业定制软件产品。

企业都喜欢年轻的程序员,年轻的程序员能加班,因为没有什么后顾之忧,年长的程序员,一般都有了家庭,有了小孩,在加班方面也确实比加上年轻的程序员,而且如果程序员年龄超过 35岁,也确实后面找工作非常难,所以人家说不管什么时候都要给自己留有后手,那么程序员的后手是什么呢,就是如果你有业余时间,还是需要接一些外面的软件做做,一方面是提升一下自己的能力,另外也可以降低一些当前飞涨的物价带来的支出影响,减轻家庭的资金压力。

回顾2023年,在技术上也学到了不少东西,不管是.net 、 java  都能开发与部署,也算是对2023年的一份答卷了,

当然在2024年还得继续努努力,在桌面端的前提下,在手机app端也要做点东西出来,这样企业在选择的时候,即有桌面又有手机端就完美了,2023年年终创建的华动软件工作室网站( www.huadongsoft.com )内容非常简单,还请光临!

以上都是随笔,如有不当处,还望海涵!

Mysql性能优化这5点你知道吗?简单却容易被初学者忽略!

文编|JavaBuild

哈喽,大家好呀!我是JavaBuild,以后可以喊我鸟哥,嘿嘿!俺滴座右铭是不在沉默中爆发,就在沉默中灭亡,一起加油学习,珍惜现在来之不易的学习时光,等工作之后,你就会发现,想学习真的需要挤时间,厚积薄发啦!

在日常工作中,我们常用的数据库无非是Mysql、Oracle、SqlServer、DB2这几种(仅针对关系型数据库中),对于我们来说,数据库的性能优化是一个重点问题,也是很多公司面试时喜欢提及的,这里总结了一些比较常见,但又相对容易忽略的部分,供大家批判学习。

1、切勿使用select * 进行全表查询

select * 会直接查询出数据表中的全量字段数据,可能很多数据并不需要,白白浪费了数据库资源,并且select * 不会走覆盖索引,可能会出现回表操作,在单表数据量较大情况下,导致sql查询的效率低下。

2、尽量用union all 替代union

union组合查询,可以不获取几张表排重后的数据结果。
union all 是用来获取几张表的全量数据,不会排重,包含重复数据。
在union排重的过程中,需要遍历、排序和比较,耗时耗资源。所以,若非一些不可有重复数据的业务场景外,尽量选择union all。

3、小表驱动大表

何为小表驱动大表?顾名思义,两多表联合查询时,用数据量小的表去驱动数据量大的数据表,譬如:有user和order两张表,order表为100万数据量,user表有100条数据,此时想查一下有效用户下过的订单情况。
有如下两种实现方式:

这两种方式,其实使用in查询的效果更好,因为,执行顺序是先执行in中子查询,然后再执行外面的雨具,此时in中数据量少,查询速度快。
当使用exists时,会优先查询左侧的主查询,然后将查询出的结果和右边的语句进行匹配,用100万条数据与100条数据匹配结果,显然效率会查很多。
这种做一个小总结:
1:in关键字适应于左边大表,右边小表;
2:exists关键字适应于左侧小表,右边大表;

后面我们还会提及的join连接查询,也同样是遵循小表驱动大表规则。

4、用连接查询代替子查询

在Mysql中,如果想通过多张表查询数据,一般会使用子查询或者连接查询的方式进行处理(在Mysql8.0之后支持与Oracle相同的WITH AS语法进行数据分块处理 )

通过in关键字实现的子查询方式,需要先查询出内层语句的结果,作为外层语句的过滤条件使用,在这个过程中子查询会被创建为临时表,查询结束后,删除这些临时表,如果几百上千行的sql中频繁使用的话,会多一些性能损耗。
这时候可以考虑通过连接查询,但是!在《阿里巴巴开发者手册》中对于join的使用数量限制在了3个,当一段sql中频繁使用join的话,会带来索引选择的困难,曾经在工作中,使用一个数据量较大的表进行了6次的left join 最终查询耗时很久。
这里建议大家可以采用多种方式混合使用的方式,先用with as 将核心数据进行一次处理,随后在通过 join进行联接查询,汇总结果。

5、建表时选择合适字段

  1. 能用数字类型,就不用字符串,字符的处理要比数字慢
  2. 满足使用情况下,用小类型,比如bit用来存布尔值,tinyint存枚举值
  3. 长度固定的字符串字段,用char类型
  4. 长度可变字符串用varchar类型
  5. 金额字段用decimal,避免精度丢失