xiaozhigang

长风破浪会有时,直挂云帆济沧海。

现在chatgpt这么火,我们也可以通过人工智能为我们的工作提供便利。

可行性

1、代码开源,不需要冲头开发,基本拿来可用。

2、单业务领域小数据量训练的可行性,单个公司一般聚焦于一个领域业务,多领域公司可以下分到各部门。单业务领域的训练数据就会小很多,基本可以内部清洗提供。

安全性

1、内部服务器部署,机器人服务部署于内部服务器,保证数据安全性。

交互性

1、通过开放接口和外部交互,如钉钉,微信。降低交互成本,提升交互沟通效率。

优越性

1、人工智能的参与,提升员工工作效率,通过员工和人工智能沟通可以提升员工的工作效率,比如程序员可以问个代码怎么写啊类似的,不做缀叙。

2、部署在内部服务器的化,还可以通过其和内部任务交互,比如日常的一些小任务,可以程序化之后,部署服务器,暴露接口和人工智能交互,我们通过聊天软件通知智能机器人,让他去触发这些任务。

3、降低工作量的重复,不知道大家有没有遇到过这样的情况,有时候,想请同事帮忙处理一下什么问题或这任务,同事由于有之前处理的经验或者记录,一两分钟可能就处理好了,而自己从头开始处理的话可能需要一两个小时。如果我们把这种任务程序化之后,交由机器人触发,我们需要的时候只需要以聊天的形式,通知机器人触发任务,那得多爽,这样就可以提高工作效率。

4、提升人工智能的准确性,从而提高员工的效率,员工在和人工智能交互的过程也是一种训练,而且还是一种监督训练,可以提供人工智能的回答准确性,从而可以提高下次和人交互的准确性。

机会点

当此人工智能在单个领域训练的准确性很高的,可以考虑将此接口暴露出去,商业化,从而盈利。

​ 在大模型大行其道的今天,学习使用大模型已经是我们程序猿不可或缺的一部分。网传的大模型神乎其神,貌似无所不能,那么大模型的底层到底是什么,是怎么一步一步的发展到今天的大模型的呢。其实大模型的底层本质就是深度学习,只不是跟我们了解的深度学习不一样的是大模型的参数比较多,甚至可以说多的可怕。了解完深度学习之后你就知道为什么参数可以作为大模型的一个指标了。

​ 其实深度学习大致可以划分成三步:构建函数模型、计算缺失值、优化。

案例背景

​ 已知店铺A,11月份每一天的每一天的营业额,我们怎么计算预测12月前3天的营业额。

image-20241230182713456

简单模型预测

​ 按照我们上述说的三个步骤,将整个过程划分成三步,构建函数模型,计算缺失值,计算参数。

构建函数模型

​ 这里我们先构建一个简单的预测模型,其实预测模型就是一个关系函数,就是被预测的结果和前面输入之间的关系函数,在这里就是12月前3天的营业额和11月营业额之间的关系。关系函数我们表示为 y=f(x),在这里我们先构建一个最简单的预测模型,假设后一天的营业额和前一天的营业额之间的关系函数是 y=ax+b,y是后一天我们预测的营业额,x是当日的营业额。

计算缺失值

​ 现在我们知道了营业额之间的预测函数和11月整个月的营业额,这样我们就可以计算出整个11月真是营业额和我们预测函数计算出来的预测营业额之间的差值L。
$$
L=\frac{1}{N} \Sigma(|y- \hat{y}|)
$$
​ 上面的公式,就是11月的预测值和实际值的差值求平均值。如果我们随机选取一个较小的a、b的初始值,就能计算出缺失值L的值,当我们不停的变幻a、b值之后,就会得到不同的L值。不同的a、b值和L可以构建出下图这样一个统计图,其中蓝色区域是L的较小值,红色是交大值。

image-20250102085812272

计算参数

​ 根据公式,我们是能算出缺失值L的,但是由于我们的预测模型是 y=ax+b,那么我们的缺失值的结果这也一定包含着这两个参数,换而言之就是这两个参数影响着我们的计算结果缺失值L。实际情况中,我们总是希望误差越小越好,也就是缺失值L越小越好。

​ 在数学中,我们求最小值,一般都和斜率相关,找到L的斜率等于0的值,我们应该就能找到一个合适a、b值。我们将L对a、b求偏导,就能找到L相对小的点。这样也就得出了合适的a、b值,从而得到了一个合适的预测函数y=ax+b。

image-20250102090855503

优化

​ 上面我们只是选了一个最简单的折线模型去计算预测值,折线模型只是最简单的模型,在本质上就有所不足,不能很好的预测多参数曲线值。如下图,如果我们用折线模型去模拟计算那条红色的折线,就不是很好计算模拟。如果我们多加几个参数,就比较好计算模拟了。

​ 可以使用sigmoid(x)的函数(下图蓝色折线1),多几个参数模拟出红色折线。蓝色折线0、1、2、3相加就等于红色折线,折线0是一个常数a,折线2是 csigmoid(x+b),折线3是dsigmoid(x+e)。从而得出:*

y=sigmoid(x) + a + csigmoid(x+b) + d*sigmoid(x+e)

$$
y=b+\sum_iC_i*sigmoid(b_i+w_ix)
$$
同时我们可以看到,销售额呈周期性变化。我们可以通过一个周期内销售额去计算预测值,公式如下。
$$
y=b+\sum_ic_isigmoid(b_i+\sum_jw_{ij}x_j)
$$

image-20250102180023235

​ 我们根据上面的公式可以计算出预测值,上述的公式5得出,如果我们计算出在得知前三个销售额的情况下,预测出的销售额。

image-20250112154212786

​ 上面的演示结果可以得出
$$
y = b + \sum_i c_i sigmoid(r_i)
$$
​ 如果我们将 sigmoid(r) 看出一个整体的话,可以简化成
$$
y = b + c^Ta
$$
​ 整个公式:
$$
y=b+c^T
a = b+c^T * \sigma(b+wx)
$$
​ 注意上面的两个b不是同一个值。

​ 整个计算逻辑,如图展示:

image-20250112164453491

​ 上面的计算模型只是模拟计算了一层,我们可以模拟计算多层,深度学习的本质就是让我们模拟计算跟多的层次以达到更好的效果,如果我们模拟计算两层的话,可以将我们第一层计算出来的a,再来一次计算。

image-20250112165402027总结

总结

​ 整个深度学习的过程就是推导出一个合适的计算模型。推导模型的过程可以分为三步:构建函数模型,计算缺失值,计算参数。在后面的优化过程中,我们总共优化了三个点:

1、模型替换,更换成了一个更有弹性的模型

2、增加模型输入,以上述销售额为例,从前一天的销售额预测后一天的销售额变成前几天的销售额预测后一天

3、增加训练层数,将一层计算变成多层计算

下面谈一谈我对chatGPT的简单认知,这也是看了许多资料总结出来的,没有去实际去研究chatGPT的代码,我姑且言之,有兴趣的同学姑且听之。

去年chatGPT大火,才让我们对人工智能有了更深一步的了解。之前认为的智能聊天就是像那些客服机器人一样,反反复复就那么几句话,跟智障一样,现在看到chatGPT这么厉害,宛若神明。

归根到底的数学概率

​ 其实大家可以简单理解,chatGPT的语言生成是一个数学概率模型,他的一个词语到生成下一个词语是采用概率最大的词语生成,就比如说,你输入一堆数据提供chatGPT训练,其中词语A后面接词语B的次数最多也就是概率最大,那么下次chatGPT给你生成回复的时候词语A后面接词语B的概率也最大。当然这也是简单说,实际肯定没这么简单。如下图,伟大的国家 这个概率是99%,拎一个选项是1%。那自然会生成 中国是个伟大的国家

img

语言模型的两个方向

​ 其实在语言模型这块一直有两个方向,一个是语义理解,一个是语句生成。语义理解是谷歌主要研究的方向,这个类似于完形填空。而语句生成是0penAI的主要方向,也就是我们现在看到的chatGPT,这个类似于写作文。这两种的应用环境和使用的算法也是不一样的,简单的说一下,语义理解,是根据前后文,两个维度计算出中间的缺失,谷歌已经做到了很高的准确率,这就对我们英语考试中的完形填空很友好了。而语句生成就跟我们写作文一样,从头写到尾,只有一个维度支撑。这就是谷歌Bert和openAI的ChatGPT的差别,双向和自回归。

img

数学+技术

​ Bert和ChatGPT都是基于Transformer实现的,啥是Transformer呢,简单理解就是我们上面说的根据概率最大生成文字。只不过这生成的实现很复杂,大概说-下,我们输入的句子会被拆分成一个一个的单词(token),根据这些单词计算向量权重,最后根据这些解析拆分后的向量权重计算概率生成输出。这些向量是怎么计算的呢,我们看个例子,国王-男人+女人=女王,这种向量的计算是不是很有意思。

img

监督学习

​ 当然上面的过程也只是其中的训练的一环,还有重要的监督学习。ChatGPT根据概率输出的东西很难保证准确性,毕竟是没有思想的机器,所以这时候就需要我们监督学习,给他的输出打分,正确的分数就高,错误的分数就低,这要提高了正确回答的权重,也就影响了概率和输出。

img

总结

​ 网上好多资料说了一大堆高大尚的名词:深度学习、循环神经网络、自然语言处理技术、注意力机制、损失函数。这些都是具体实现,不做ai 的也没必要深入了解,知道chatGPT的大概原理也就够了。目前好多人说chatGPT的出现会对现在的社会造成巨大的冲击,绝大多数人都会失业。我想说这并不一定,上面也说了人工智能是根据概率推算结果的,这个概率是根据已有事件计算的,类似于绝大多数人说啥,他也会说啥。这也就是说,他是没有创造力的,他只能帮我们整理已有事件,不会突破已有的事件。比如说当绝大多数人都认为地球是-个平面的时候,chatGPT也会认为地球是一个平面,不会有思考发现地球是个球体。人工智能只是解放了我们生产力,让我们有更多的时间去完成一些创造性的事情。但是如果我们一直在做这种重复性的事情,没有思考创新,chatGPT的出现对我们来说绝对会是个巨大的灾难,至少摸鱼的机会会大大减少。

img

简述

在kafka的使用过程中,消息传递有下图三个步骤,消息的丢失也就在这三个步骤中:发送过程中丢失、同步过程中丢失、拉取过程中丢失。

image-20240331213103843

发送过程中丢失

发送方式

生产者的发送方式有三种:

1、简单发送,不关心发送结果,所以发送失败消息丢失也不知道。

1
2
3
4
5
6
7
8
ProducerRecord<String,String> record = new ProducerRecord<>("topicName","key","value");
try{
//这里只是把消息放进了一个缓冲区中,然后使用单独的线程将消息发送到服务端
producer.send(record);
}
catch(Exception){
e.printStackTrace();
}

2、同步发送,等待发送返回

1
2
3
4
5
6
7
8
9
ProducerRecord<String,String> record = new ProducerRecord<>("topicName","key","value");
try{
//send方法返回的是Future<RecordMetaData> 对象,然后我们可以调用get()方法等待响应
Future<RecordMetaData> future = producer.send(record);
future.get();
}
catch(Exception){
e.printStackTrace();
}

3、异步发送,执行回调方法

1
2
3
4
5
6
7
8
private class DemoProducerCallback implements Callback{
@override
public void onCompletion(RecordMetadata recordMetadata,Exception e){
//发生错误的回调方法,可以写入日志,或写入DB通过其它线程重重试,保证最终的数据送达
}
}
ProducerRecord<String,String> record = new ProducerRecord<>("topicName","key","value");
producer.send(record,new DemoProducerCallback()))

在这三种发送方法中,第一种方法,无返回,不感知,所以也无法保证消息不丢失。所以要保证消息不丢失,只能选择第二种或者第三种,一般情况下更推荐第三种。

acks参数设置

在生产者中有acks参数,该参数指定了kafka的多少个副本同步后才算消息发送成功。该参数取值范围:

1、acks=0,表示生产者在消息后不管有没有在leader磁盘上落盘,就认为消息发送成功。

2、acks=1,表示生产者在消息后,在leader磁盘上落盘,就认为消息发送成功,不管其他follower有没有同步。

3、acks=all,表示生产者在消息后,在leader磁盘上落盘,其他follower都同步落盘, 认为消息发送成功。

image-20240331222238322

小结

通过设置发送方式和acks参数,可以保证在发送方式中不丢失,甚至acks参数都可以保证后面的同步过程中消息不丢失。

发送方式一般设置为异步发送,acks参数默认设置为1。

同步过程中丢失

在同步过程中,除了上面说的答acks参数,还有其他副本机制保证消息不丢失。避免leader节点的崩溃导致消息的丢失。

broker中的配置项,unclean.leader.election.enable = false,表示不允许非ISR中的副本被选举为首领,以免数据丢失。

ISR:是指与leader保持一定程度(这种范围是可通过参数进行配置的)同步的副本和 leader 共同被称为ISR

OSR:与leader同步时,滞后很多的副本(不包括leader)被称为OSR

AR,分区中所有的副本统称为AR。AR = ISR + OSR

拉取过程中丢失

设置 enable.auto.commit = false,在consumer端消费消息操作完成以后再手动提交 offset,类似于下文中的代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void consumerMsg(){
while(true){
//这里的poll(100)指的是kafka server端没有消息时,连接等待的时间,超过该时间立即返回空给consumer
ConsumerRecords<String,String> records = consomer.poll(100);
for(ConsumerRecord<String,String> record : records){
// 这里是消费消息的逻辑(简单逻辑输入到控制台)
System.out.printIn(record.value));
//提交偏移量
try{
consumer.commitSync(); //同步提交 如果异步的话,可以使用 consumer.commitAsync();
}
catch(CommitFailedException ex){
log.error("commit fail");
}
}
}
}

需求描述

计划将原有的代码抽取成可复用的原子能力jar包,一个业务可能需要调用多个原子能力jar包,jar包调用链配置在配置文件中。

实现外部调用接口,通过原子能力调用链实现原子能力包调用。

主要目的:1、实现能力抽取复用,2、实现原子能力编排。

1、没有架构的架构

外部业务通过接口调用service服务,service服务内部通过配置文件调用封装好的jar包。kafka作为消息中间件,为service和运行的jar收集日志。

redis作为缓存中间件,为service和运行的jar提供缓存服务,而MySQL为为service和运行的jar提供持久化数据存储。

image-20240408215632370

2、抽取engine服务

所有的逻辑都杂糅在service服务内,很明显不合适,如果需要扩展多节点的化,那整个service服务都要部署扩展。

其实扩展多节点,主要是为了可复用的jar包能在多节点运行,提高运行效率。

那么我们抽取出一个engine服务,只做加载运行jar包,这样扩展多节点的话,只要扩展engine服务就行。

在抽取完之后,engine和service就不是一个服务了,service调用engine可以通过http请求,但是engine还得向service返回心跳。最好的就是通过kafka来实现这个心跳检测。

image-20240408221643046

3、拆分service

为了提高可拓展性,我们抽出了engine服务,现在的service其实还负责两件事,第一个就是对外的接口暴露,第二个就是内部原子能力的调度。

现在为了使职责更内敛,我们将service再次拆分,拆分成:getaway和schedule

image-20240408225007616

4、新增注册中心

现在又多了getaway和schedule服务,那么服务间的检测就不能只用kafka做的心跳检测了,得增加一个注册中心,这样都方便调度,尤其使engine有多节点部署的需求。engine和schedule都注册在nacos中,getaway和是schedule的调度都是先从nacos中获取相应的服务地址和端口,然后再开始调取相应接口。

image-20240408225340577

5、调用链的转移

原本的调用链是在配置文件中的,经过service的拆分,现在应该在schedule的配置文件中。

虽然调用链是在开发中就已经配置好的,但是我们将调用链移动到MySQL中,这样可以更好的扩展。可以通过其他途径改动MySQL中的调用链,实现实时变更。

由于调用链这种配置不会随便改动,可以通过预加载,将其加载到redis中,这样可以减小MySQL的压力。

image-20240408230847003

简介

ELK是elasticsearch、logstash、kibana软件的集合,对外是作为一个日志管理系统的开源方案,它可以从任何来源、任何格式进行日志搜索、分析与可视化展示。
基本组成软件:

  • elasticsearch:一个开源分布式搜索引擎,提供收集、分析、存储数据三大功能。

  • kibana:一个基于web的图形界面,用于搜索、分析和可视化存储在elasticsearch中的日志数据。

  • logstash:一个服务端的数据处理管道,可以从多个源中提取数据,对其进行转换,然后将其存储到Elasticsearch中。简单来说就是日志的收集、分析、过滤工具。

搭建步骤

使用版本是7.9.3,部署前我们可以先提前从官网上下载下面4个安装包:
elasticsearch-7.9.3-linux-x86_64.tar.gz
kibana-7.9.3-linux-x86_64.tar.gz
logstash-7.9.3.tar.gz

这4个组件可以部署在不同的机器上,只要机器之间是端口开放即可
本文档中elasticsearch、logstash、kibana部署在一台机器,filebeat部署在有业务服务的机器上。

  1. 创建用户和解压文件
    groupadd elk
    useradd elk -g elk
    解压文件:
    tar -zxvf elasticsearch-7.9.3-linux-x86_64.tar.gz -C /home/elk
    tar -zxvf kibana-7.9.3-linux-x86_64.tar.gz -C /home/elk
    tar -zxvf logstash-7.9.3.tar.gz -C /home/elk


    cd /home/elk
    mv kibana-7.9.3-linux-x86_64 kibana-7.9.3

    chown -R elk:elk /home/elk/elasticsearch-7.9.3
    chown -R elk:elk /home/elk/kibana-7.9.3
    chown -R elk:elk /home/elk/logstash-7.9.3

  2. 部署elasticsearch

    1. 切换到elk用户
      su - elasticsearch
      cd /home/elk/

    2. 修改elasticsearch.yml
      cd /home/elk/elasticsearch-7.9.3/config
      vi elasticsearch.yml
      在文件末尾增加以下内容
      # 集群初始主节点
      cluster.name: “es-cluster”
      network.host: 0.0.0.0
      node.name: “node-1”


      # discovery type
      discovery.type: single-node
      #discovery.seed_hosts: [“127.0.0.1”, “[::1]”]

      # 设置允许所有ip可以连接该elasticsearch
      network.host: 0.0.0.0

      # 开启监听的端口为9200
      http.port: 9200

      # 增加新的参数,为了让elasticsearch-head插件可以访问es
      http.cors.enabled: true
      http.cors.allow-origin: “*”

      # 启用密码
      xpack.security.enabled: true

      # 使用默认密码
      xpack.security.authc.accept_default_password: true
      xpack.security.transport.ssl.enabled: true
      如果需要外置存储和日志,需要修改path.data和path.logs
      path.data: /nfsc/cnas_csp_pase_hrx_id010797_vol1003_dev/elasticsearch
      path.logs: /nfsc/cnas_csp_pase_hrx_id010797_vol1003_dev/elasticsearch_logs

    3. 修改Elasticsearch占用内存
      cd /home/elk/elasticsearch-7.9.3/config
      vi jvm.options
      修改JVM参数 :
      -Xms16g
      -Xmx16g

    4. 启动es的时候有可能会报类似的下面的错误【max virtual memory areas vm.max_map_count [65530] is too low】,所以我们可以通过下面的方法去处理:
      vi /etc/sysctl.conf
      文件末尾添加一行
      vm.max_map_count=655360
      保存之后执行下面的命令加载参数
      sysctl -p

    5. 启动Elasticsearch
      切换用户,进入bin目录启动:
      cd /home/elk/elasticsearch-7.9.3/bin
      ./elasticsearch -d

    6. 为内置账号添加密码

      interactive:给用户一一设置密码

      # auto:自动生成密码
      cd /home/elk/elasticsearch-7.9.3/bin/
      ./elasticsearch-setup-passwords interactive
      
    7. 验证是否启动成功

      curl -XGET -u elastic ‘localhost:9200’

      {
        "name" : "node-1",
        "cluster_name" : "es-cluster",
        "cluster_uuid" : "8eGk8_ohRbWl8ZcNSPsFDQ",
        "version" : {
          "number" : "7.9.3",
          "build_flavor" : "default",
          "build_type" : "tar",
          "build_hash" : "c4138e51121ef06a6404866cddc601906fe5c868",
          "build_date" : "2020-10-16T10:36:16.141335Z",
          "build_snapshot" : false,
          "lucene_version" : "8.6.2",
          "minimum_wire_compatibility_version" : "6.8.0",
          "minimum_index_compatibility_version" : "6.0.0-beta1"
        },
        "tagline" : "You Know, for Search"
      }
      
  3. 部署kibana

    1. 修改配置文件,文件位置在/home/elk/kibana-7.9.3/config目录下
      su - elasticsearch
      cd /home/elk/kibana-7.9.3/config
      vi kibana.yml
      添加es的配置,由于是都在同一台机器所以用localhost,如果是不同机器,就换成对应的ip即可
      # kibana端口
      server.port: 5601


      # 允许所有ip访问
      server.host: “0.0.0.0”

      # elasticsearch所在的ip及监听的地址
      elasticsearch.hosts: [“http://localhost:9200"]
      elasticsearch.username: “kibana_system”
      elasticsearch.password: “kibana_system”

      # kibana默认创建的索引
      kibana.index: “.kibana”

      # 字符编码
      i18n.locale: “zh-CN”

      # elasticsearch加密所需配置
      xpack.reporting.encryptionKey: “a_random_string”
      xpack.security.encryptionKey: “something_at_least_32_characters”

    2. 启动kibana
      cd /home/elk/kibana-7.9.3/bin
      ./kibana &

    3. 访问kibana
      http://IP:5601/

  4. 部署Logstash

    1. 修改Logstash的YML配置文件
      cd /home/elk/logstash-7.9.3/config/
      vim logstash.yml
      修改以下内容
      # 配置自动刷新
      config.reload.automatic: true
      config.reload.interval: 20s


      # 配置可任意地址都可访问
      http.host: 0.0.0.0
      http.port: 9600

      # 配置es的访问密码
      xpack.monitoring.enabled: true
      xpack.monitoring.elasticsearch.username: logstash_system
      xpack.monitoring.elasticsearch.password: logstash_system
      xpack.monitoring.elasticsearch.hosts: [“http://localhsot:9200"]

    2. 修改 pipelines配置文件

      • pipeline.id: gmp-logs
        queue.type: persisted
        path.config: “/home/elk/logstash-7.9.3/conf.d/*.config”
    3. 创建日志的config文件
      touch /home/elk/logstash-7.9.3/conf.d/logstash-beats.conf
      写入文件内容
      input {
      beats {
      port => 5044
      }
      }


      filter {
      grok {
      match => [
      “message”, “%{TIMESTAMP_ISO8601:timestamp_string}%{SPACE}%{GREEDYDATA:line}”
      ]
      }
      date {
      match => [“timestamp_string”, “ISO8601”]
      }
      mutate {
      remove_field => [message, timestamp_string]
      }
      }

      output {
      elasticsearch {
      hosts => [“http://localhost:9200"]
      user => elastic
      password => “elastic”
      }
      stdout {
      codec => rubydebug
      }
      }

    4. 启动Logstash(root用户)
      cd /home/elk/logstash-7.9.3/bin
      sh logstash -f ../conf.d/logstash-beats.conf &

简述

redis有两种持久化方式:RDB(Redis DataBase)持久化和 AOF(Append Only File)。

RDB:通过创建数据的快照,在指定时间间隔内将redis某个时刻的数据状态保存到磁盘RDB文件中。

AOF:通过记录每个操作命令并将其追加到AOF文件中,恢复时通过执行这些命令来重建数据。

image-20240331163759309

RDB持久化

RBD以快照的方式保存全量的数据,可通过save和bgsave两个命令来触发持久化。

1、save命令:会同步地将 Redis 的所有数据保存到磁盘上的一个 RDB 文件中。这个操作会阻塞所有客户端请求直到 RDB 文件被完全写入磁盘。

2、bgsave命令:会在后台异步地创建 Redis 的数据快照,并将快照保存到磁盘上的 RDB 文件中。这个命令会立即返回,Redis 服务器可以继续处理客户端请求。

image-20240331165755573

AOF持久化

AOF以追加的方式保存增量的数据,AOF 的主要作用是解决了数据持久化的实时性,目前已经是 Redis 持久化的主流方式。

AOF 的工作流程操作:命令写入 (append)、文件同步(sync)、文件重写(rewrite)、重启加载 (load)。

image-20240331171433901

流程如下:

1)当 AOF 持久化功能被启用时,Redis 服务器会将接收到的所有写命令(比如 SET, LPUSH, SADD 等修改数据的命令)追加到 AOF 缓冲区(buffer)的末尾。

2)为了将缓冲区中的命令持久化到磁盘中的 AOF 文件,Redis 提供了几种不同的同步策略:

  • always:每次写命令都会同步到 AOF 文件,这提供了最高的数据安全性,但可能因为磁盘 I/O 的延迟而影响性能。
  • everysec(默认):每秒同步一次,这是一种折衷方案,提供了较好的性能和数据安全性。
  • no:不主动进行同步,交由操作系统决定何时将缓冲区数据写入磁盘,这种方式性能最好,但在系统崩溃时可能会丢失最近一秒的数据。

3)随着操作的不断执行,AOF 文件会不断增长,为了减小 AOF 文件大小,Redis 可以重写 AOF 文件:

  • 重写过程不会解析原始的 AOF 文件,而是将当前内存中的数据库状态转换为一系列写命令,然后保存到一个新的 AOF 文件中。
  • AOF 重写操作由 BGREWRITEAOF 命令触发,它会创建一个子进程来执行重写操作,因此不会阻塞主进程。
  • 重写过程中,新的写命令会继续追加到旧的 AOF 文件中,同时也会被记录到一个缓冲区中。一旦重写完成,Redis 会将这个缓冲区中的命令追加到新的 AOF 文件中,然后切换到新的 AOF 文件上,以确保数据的完整性。

4)当 Redis 服务器启动时,如果配置为使用 AOF 持久化方式,它会读取 AOF 文件中的所有命令并重新执行它们,以恢复数据库的状态。

两者优缺点

RDB-优点

  1. 集中备份,则文件紧凑, dump.rdb,非常适合备份、全量复制的场景。
  2. 容灾性好,RDB 文件可以直接拷贝使用,用于容灾恢复。
  3. 恢复速度快,RDB文件集中紧凑, 则速度快

RDB-缺点

  1. 实时性低,RDB 是间隔一段时间进行持久化,无法实时持久化。如果在这时间段发生故障,则数据会丢失。
  2. 存在兼容问题,Redis 演进过程存在多个格式的 RDB 版本,存在老版本 Redis 无法兼容新版本 RDB 的问题。

AOF-优点

  1. 实时性好,aof 持久化可以配置 appendfsync 属性,有 always,每进行一次命令操作就记录到 aof 文件中一次。
  2. 通过 append 模式写文件,即使中途服务器宕机,可以通过 redis-check-aof 工具解决数据一致性问题。

AOF-缺点

  1. AOF 文件比 RDB 文件大,且 恢复速度慢
  2. 数据集大 的时候,比 RDB 启动效率低

两者如何选择

1、只选RDB,可能会有数分钟的数据丢失。

2、只选AOF,不利于备份,恢复速度也慢。

3、两者配合,两种持久化方式都选,这种情况下Redis重启时会优先载入AOF文件来恢复数据,因为AOF文件的数据集比RDB全。

4、都不选,不持久化。

故障数据恢复

Redis 启动时加载数据的流程:

1、判断AOF开启且AOF文件是否存在,存在则优先加载

2、如果AOF关闭或者不存在,则加载RDB文件

3、文件加载成功后则Redis启动成功

4、加载失败,则启动失败

image-20240331174017060

REST


RESTful本身是一种风格而不是规范,本文为该风格的规范实现的最佳实践,本文档详细说明了HTTP RESTful API的定义和使用规范,作为接口调用者和实现者的重要参考。

接口风格

遵循RESTful设计风格,同时控制复杂度及易于使用,仅遵循大部分原则。 遵循原则:

  • 使用https协议
  • 版本号放入URL或Header
  • 只提供json返回格式
  • post,put上使用json作为输入
  • 使用http状态码作为错误提示
  • Path(路径)尽量使用名词,不使用动词,把每个URL看成一个资源
  • 使用HTTP动词(GET,POST,PUT,DELETE)作为action操作URL资源
  • 过滤信息
    • limit:指定返回记录数量
    • offset:记录开始位置
    • direction:请求数据的方向,取值prev-上一页数据;next-下一页数据
    • page:第几页
    • per_page:每页条数
    • total_count:总记录数
    • total_pages:总页数,等于page时,表示当前是最后一页
    • sort:column1,column2排序字段
    • orderby:排序规则,desc或asc
    • q:搜索关键字(uri encode之后的)
  • 返回结果
    • GET:返回资源对象
    • POST:返回新生成的资源对象
    • PUT:返回完整的资源对象
    • DELETE:返回一个空文档
  • 速率限制
    • X-RateLimit-Limit: 每个IP每个时间窗口最大请求数
    • X-RateLimit-Remaining: 当前时间窗口剩余请求数
    • X-RateLimit-Reset: 下次更新时间窗口的时间(UNIX时间戳),达到下个时间窗口时,Remaining恢复为Limit

未遵循原则:

  • Hypermedia API(HATEOAS),通过接口URL获取接口地址及帮助文档地址信息
  • 限制返回值的域,fields=id,subject,customer_name
  • 缓存,使用ETag和Last-Modified

参考:

模块和版本说明

接口模块相互对立且有版本管理,模块名作为APP配置项进行存储,每个模块的版本号version和endpoint在应用初始化时调用api模块信息接口(通过传递客户端应用名称和版本号获取各个API模块的endpoint和version)获取并存储。

  • 示例模块及最新版本号:

模块模块用途最新版本号account帐户v1sms短信v1open一些开放接口,不需要公共参数v1

公共参数

Headers

公共请求参数是指每个接口都可能需要传递的参数,公共参数通过header传递。

参数是否必须说明及header格式app所有接口必须请求客户端应用标识,取值*-ios、*-android、*-pc、*-h5
header格式:
X-Co-App: $appuser_idApp登录后所有接口都传,
Web通过session机制获取用户标识
header格式:
Authorization: CoAPI base64(user_id:token)tokenApp登录后所有接口都传,
Web通过session机制获取授权访问令牌
header格式:
Authorization: CoAPI base64(user_id:token)

  • Web应用通过cookies传递session id,user_id和token无需传递,接口会从session自动获取;
  • 同一token值在App和Web各应用间通用(token即为session id);
  • APP修改user-agent,在原有user-agent的尾部添加$app/$versionNetType/$value。如:
    • Dalvik/2.1.0 (Linux; U; Android 6.0.1; MI 4LTE MIUI/V7.5.3.0.MXGCNDE) $app-android/3.0.0 NetType/4G
    • Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_2 like Mac OS X) AppleWebKit/603.2.4 (KHTML, like Gecko) $app-ios/3.0.0 NetType/WIFI
  • app取值及释义示例

app取值客户端名称【域名】admin-pc管理中心PC网页版【admin.url.com】admin-h5管理中心手机网页版【admin.url.com】admin-ios管理中心iOS版admin-android管理中心Android版

Cookies

  • 用于告知服务端是否支持Webp的Cookie:cookie name是supportWebp,取值是1(支持)和0(不支持),未传递时服务端默认取值为0。
  • Webview植入Session的Cookie:

JWT & OAuth2

  • Json Web Token可用于替代session-cookie机制。但会存在一些问题,比如为过期token强制失效问题(用户修改了密码后,无法强制其他的终端token全部失效)。
  • OAuth2是授权其他开发者访问自己应用有限权限的授权机制。

权限

  • 权限分为
    • none:无需任何授权;
    • token:需要用户登录授权,可通过header AuthorizationCookie CoSID传递;
    • admintoken:需要管理员登录授权,可通过header AuthorizationCookie CoCPSID传递;
    • token || admintoken:用户登录授权或管理员登录授权都可以;
    • sign:需要签名,一般用于服务端内部相互调用。

状态码说明

正确
接口正常访问情况下,服务器返回2××的HTTP状态码。

HTTP状态码200 OK - 表示已在响应中发出、资源更改成功(GET、PUT)201 Created - 新资源被创建(POST)204 No Content - 资源被删除(DELETE)

错误
当用户访问接口出错时,服务器会返回给一个合适的4××或者5××的HTTP状态码;以及一个application/json格式的消息体,消息体中包含错误码code和错误说明message。

  • 5××错误(500=<status code)为服务器或程序出错,客户端只需要提示“服务异常,请稍后重试”即可,该类错误不在每个接口中列出。
  • 4××错误(400=<status code<500)为客户端的请求错误,需要根据具体的code做相应的提示和逻辑处理,message仅供开发时参考,不建议作为用户提示。
  • 部分错误示例:

codemessageHTTP状态码InvalidToken未登录或授权过期,请登录401 UnauthorizedValidationError输入字段验证出错,缺少字段或字段格式有误422 Unprocessable EntityAccountNotExist账户名不存在404 Not FoundInvalidPassword密码错误401 UnauthorizedNotFound请求的资源不存在404 Not FoundAccountHasExist账户名已经存在409 ConflictMobileHasBinded手机号已经绑定其他账户409 ConflictInvalidSign参数签名验证未通过403 ForbiddenInvalidSMSCode短信验证码错误403 ForbiddenExpiredSMSCode过期的短信验证码403 ForbiddenFrequencyLimit发送过于频繁,请稍后再试403 ForbiddenTimesExceeded达到最大发送次数限制,请明天再试403 ForbiddenVerifyTimesExceeded达到最大校验次数,请明天再试403 ForbiddenRateLimitExceeded接口调用次数超过限制,请稍后再试429 Too Many Requests InternalError服务异常,请稍后再试500 Internal Server Error

参数传递

遵循RESTful规范,使用了GET, POST, PUT, DELETE共4种请求方法。

  1. GET:请求资源,返回资源对象
  2. POST:新建资源,返回新生成的资源对象
  3. PUT:新建/更新资源,返回完整的资源对象
  4. DELETE:删除资源,返回body为空
  • GET请求不允许有body, 所有参数通过拼接在URL之后传递,所有的请求参数都要进行遵循RFC 3986的URL Encode。
  • DELETE删除单个资源时,资源标识通过path传递,批量删除时,通过在body中传递JSON。
  • POST, PUT请求,所有参数通过JSON传递,可选的请求参数,只传有值的,无值的不要传递,contentType为application/json。

4种请求动作中,GET、PUT、DELETE是幂等的;只有POST是非幂等的。幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。 是非幂等是判断接口使用POST还是PUT的决定条件

注意: APP端获取json数据时,对于数值类型字段必须以数值类型转换,无论传递过来的值是否带引号。

速率限制Rate Limiting

  • 为了防止API被恶意调用,对API调用进行速率限制。
  • 速率限制为每IP每15分钟5000次(dev/qa为10W)调用(15分钟是一个时间窗口)。
  • 限制是针对所有接口模块一起计算的(Redis key为APIRL:{IP}),暂时没有特殊的模块或单个接口(未来可能有)。
  • 你可以通过每个接口返回的HTTP headers了解当前速率限制的情况:
    • X-RateLimit-Limit: 每个IP每个时间窗口最大请求数
    • X-RateLimit-Remaining: 当前时间窗口剩余请求数
    • X-RateLimit-Reset: 下次更新时间窗口的时间(UNIX时间戳),达到下个时间窗口时,Remaining恢复为Limit
  • 超出速率限制,返回以下错误

安全注意事项

  • 用户登录后用户的token;aliyun OSS的bucket、AccessKey ID与AccessKey secret;微视频的appid、sign、bucket;这些关键数据通过调用接口获得,需要在客户端以安全的方式存储。
  • 音频视频在APP内的存储,不允许被拷贝(即使越狱或root后拿走也无法使用)。

测试工具

推荐Chrome浏览器插件Postman作为接口测试工具, Postman下载地址

文档生成工具

调用示例

  • 伪代码
  • PHP

API模块信息获取

  • App配置文件中仅存储api模块名,App初始化时请求获取api模块信息,获取各个api模块的信息(endpoint和version)。

需求描述

我有一个仓库AAA,目录结构如下:

1
2
3
4
5
6
7
8
$ tree
├── .git
├── common-api
├── commons-pojo
├── commons-utils
├── account
└── payment
└── product

现在我需要把common-api、commons-pojo、commons-utils独立为一个新仓库xxx-common
从网上找到可以用”git filter-branch”和”git subtree split”两种方式独立目录为仓库,但是”git subtree split”只能独立一个目录为仓库,如果需要独立多个目录为仓库需要使用”git filter-branch”。

阅读全文 »
0%