轻量级HTTP请求库:request-my

轻量级HTTP请求库:request-my
做项目的初衷: 大家经常用http请求,以前用的比较多的是jq的ajax,现在用的多的是axios,我就是想写一个比较简单的http请求工具,平时html也能使用,就是简单使用及测试用的。希望大家多多使用。
安装
使用 npm:
$ npm install request-my –save
例子
UsageES2015
import {api} from ‘request-my’;
api.ajax({
    url:’xxx:3000/post’,
    type:’post’,
    data:{
      name:’shibin’
    },
    success:function(res){
      console.log(res)
    },
    error:function(err){
      console.log(err);
    }
})
浏览器使用The dist folder contains index.js.
<script type=”text/javascript” src=”./dist/request-my.min.js”></script>
<script>
api.ajax({
    url:’xxx:3000/post’,
    type:’post’,
    data:{
      name:’shibin’
    },
    success:function(res){
      console.log(res)
    },
    error:function(err){
      console.log(err);
    }
})
</script>

Java多线程中的AQS简述

Java多线程中的AQS简述
1. 抽象的队列式的同步器,AQS(AbstractQueuedSynchronizer)定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如常用的ReentrantLock.
2. AQS提供一个框架,用于实现依赖先进先出(FIFO)等待队列的阻塞锁和相关同步器(信号量,事件等)。 该类被设计为大多数类型的同步器的有用依据,这些同步器依赖于单个原子int值来表示状态。 子类必须定义改变此状态的受保护方法,以及根据该对象被获取或释放来定义该状态的含义。 给定这些,这个类中的其他方法执行所有排队和阻塞机制。
3. 它维护了一个volatile int state(代表共享资源)和一个FIFO线程等待队列(多线程争用资源被阻塞时会进入此队列)。
state的访问方式有三种:
(1)protected final int getState(): 返回同步状态的当前值。 这个操作具有一个volatile读取的记忆语义。
(2)protected final void setState(int newState): 设置同步状态的值。 这个操作有一个volatile写的内存语义。(newState – 新的状态值)
(3)protected final boolean compareAndSetState(int expect, int update): 如果当前状态值等于期望值,则将同步状态原子地设置为给定的更新值。 此操作具有volatile读写的记忆语义。(expect – 期望值 update – 实际值);
4. AQS定义两种资源共享方式:Exclusive(独占,只有一个线程能执行,如ReentrantLock)和Share(共享,多个线程可同时执行,如Semaphore/CountDownLatch)。
  不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源state的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。
5. 自定义同步器实现时主要实现以下几种方法:
(1)isHeldExclusively():如果同步仅针对当前(调用)线程进行保存,则返回true,否则false。表示线程是否正在独占资源。只有用到condition才需要去实现它。
(2)tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。
(3)tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。
(4)tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
(5)tryReleaseShared(int):共享方式。尝试释放资源,成功则返回true,失败则返回false。
6. 以ReentrantLock为例,state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证state是能回到零态的。
7. 以CountDownLatch以例,任务分为N个子线程去执行,state也初始化为N(注意N要与线程个数一致)。这N个子线程是并行执行的,每个子线程执行完后countDown()一次,state会CAS减1。等到所有子线程都执行完后(即state=0),会unpark()主调用线程,然后主调用线程就会从await()函数返回,继续后余动作。
8. 一般来说,自定义同步器要么是独占方式,要么是共享方式,它们也只需实现tryAcquire-tryRelease、tryAcquireShared-tryReleaseShared中的一种即可。但AQS也支持自定义同步器同时实现独占和共享两种方式,如ReentrantReadWriteLock。

React中事件的写法总结

React中事件的写法总结
首先我们先查看官方规则或者官方文档的介绍,React的事件处理和DOM元素很相似,但是语法上是有不同的:
1、react事件采用驼峰命名法,而不是纯小写。
驼峰命名法(camelCase):命名的全部全称, 首个单词全部小写后面每个单词的首字母大写。
eg: getElementById onClick
2、使用JSX语法的时候需要传入一个函数作为事件处理函数, 而不是一个字符串
了解基本的以后 , 下面我们利用九个例子来进行事件的巩固:
1、普通匿名函数的直接绑定
<button onClick={function(){alert(‘直接绑定’)}}>按钮1</button>
给onClick后面直接绑定函数,点击以后,就会执行函数里面的内容。
2、使用箭头函数代替匿名的绑定
<button onClick={()=>{
       alert(“点击触发匿名函数”);
}}>按钮2</button>
3、箭头函数中获取事件源(等同于原生的event对象)
<button onClick={(e)=>{
      e.target.style.color=”red”;
}}>点击以后按钮就会变成红色</button>
e就是默认的事件参数 e中的target表示事件发生的目标元素 点击以后按钮就会变成红色
4、将匿名函数分离封装
<button onClick={
        this.show
}>点击调用外面的函数</button>
点击按钮以后 触发外面声明的show方法 show方法定义如下
constructor(props){
        super(props);
        this.state={
            num:10
        }
    }
    show(){
            alert(“按钮4被点击 但是里面this会脱离上下文关系”);
            alert(this.state.num);//报错 this是undefined
    }
可以弹框提示 , 但是会有this指向的问题 , 接下来 我们看第五步的加强
5、利用箭头函数 对this指向进行加强 改写的代码如下
<button onClick={
         this.show//参数问题就看第六点
}>调用函数</button>
点击以后num就能得到改变
6、再来研究箭头函数中参数的问题 如果调用的时候带走参数
<button onClick={//show6能带参数
        ()=>{
              this.show6(“我是参数”)
         }
}>调用带有参数的函数</button>
方法定义的如下 show6为一个箭头函数, content就是形参 ,接受的值为我是参数
show6=(content)=>{
        alert(content);
 }
7、事件函数触发是带参数和事件源
<button onClick={//show7能带参数 还能带事件源
         (e)=>{
               this.show7(“7777”,e);
         }
  }>带走参数和事件源</button>
show7中第二个参数e需要特别处理, 在(e)中声明 才能带出去, 不然e会是没有定义。
方法的定义部分
show7=(content,e)=>{
        e.target.innerHTML= content;
  }
content就是参数”7777″ ,e就是事件源, 通过事件源可以找到目标元素, 然后更新里面的内容
8、不使用箭头函数 使用bind加强(bind里面的*个参数 表示bind前面函数声明里面this的指向)
<button onClick={this.show8.bind(this)}>bind加强</button>
方法定义的地方
show8(){
        alert(“bind绑定”);
 }
9、改写bind 贴近官方推荐写法
<button onClick={this.show9} >bind写法</button>
在show9定义中
constructor(props){
        super(props);
        this.state={
            num:10
        }
        this.show9 = this.show9.bind(this);//bind的提升 调用的写法和8就不一样了
    }
    show9(){
        alert(this.state.num);
    }
​总结:
react中事件的使用和原生的事件使用*其相似,我们需要解决,函数函数的声明和参数的传递 使用,根据自己的实际情况,合理使用就可以了,不管是箭头函数和普通函数,实际都是殊途同归。

Nodejs 中基于 Stream 的多文件合并实现

Nodejs 中基于 Stream 的多文件合并实现
本文先从一个 Stream 的基本示例开始,有个初步认识,中间会讲在 Stream 中什么时候会出现内存泄漏,及如何避免*后基于 Nodejs 中的 Stream 实现一个多文件合并为一个文件的例子。
一个简单的 Stream 操作
创建一个可读流 readable 一个可写流 writeable,通过管道 pipe 将可写流绑定到可读流,一个简单的 Stream 操作就完成了。
const fs = require(‘fs’);
const readable = fs.createReadStream(‘./test1.txt’);
const writeable = fs.createWriteStream(‘./test2.txt’);
readable.pipe(writeable);
看下 pipe 这个方法两个参数:
destination:是一个可写流对象,也就是一个数据写入的目标对象,例如,上面我们创建的 writeable 就是一个可写流对象
options:end:读取结束时终止写入流,默认值是 true
readable.pipe(destination[, options])
默认情况下我们是不需要手动调用写入流的 end 方法关闭的。
现在我们改一下,设置 end 为 false 写入的目标流将会一直处于打开状态, 此时就需要监听可读流的 end 事件,结束之后手动调用可写流的 end 事件。
// readable.pipe(writeable);
readable.pipe(writeable, {
  end: false,
});
readable.on(‘end’, function() {
  writeable.end(‘结束’);
});
还需要注意一点如果可读流期间发生什么错误,则写入的目标流将不会关闭,例如:process.stderr 和 process.stdout 可写流在 Nodejs 进程退出前将永远不会关闭,所以需要监听错误事件,手动关闭可写流,防止内存泄漏。
Linux 下一切皆文件,为了测试,在创建可读流时,你可以不创建 test1.txt 文件,让可读流自动触发 error 事件并且将 writeable 的 close 方法注释掉,通过 linux 命令 ls -l /proc/${pid}/fd 查看 error 和非 error 前后的文件句柄变化。
readable.on(‘error’, function(err) {
  console.log(‘error’, err);
  // writeable.close();
});
console.log(process.pid); // 打印进程 ID
setInterval(function(){}, 5000) // 让程序不中断,进程不退出
以下为触发 error 错误下 test2.txt 这个文件 fd 将会一直打开,除非进程退出,所以重要的事情再说一遍,一定要做好错误监听手动关闭每个写入流,以防止 “内存泄漏”。
l-wx—— 1 root root 64 Apr 10 15:47 19 -> /root/study/test2.txt
多个文件通过 Stream 合并为一个文件
上面讲了 Stream 的基本使用,*后提到一点设置可读流的 end 为 false 可保持写入流一直处于打开状态。如何将多个文件通过 Stream 合并为一个文件,也是通过这种方式,一开始可写流处于打开状态,直到所有的可读流结束,我们再将可写流给关闭。
streamMerge 函数为入口函数
streamMergeRecursive 函数递归调用合并文件
const fs = require(‘fs’);
const path = require(‘path’);
/**
 * Stream 合并
 * @param { String } sourceFiles 源文件目录名
 * @param { String } targetFile 目标文件
 */
function streamMerge(sourceFiles, targetFile) {
  const scripts = fs.readdirSync(path.resolve(__dirname, sourceFiles)); // 获取源文件目录下的所有文件
  const fileWriteStream = fs.createWriteStream(path.resolve(__dirname, targetFile)); // 创建一个可写流
  streamMergeRecursive(scripts, fileWriteStream);
}
/**
 * Stream 合并的递归调用
 * @param { Array } scripts
 * @param { Stream } fileWriteStream
 */
function streamMergeRecursive(scripts=[], fileWriteStream) {
  // 递归到尾部情况判断
  if (!scripts.length) {
    return fileWriteStream.end(“console.log(‘Stream 合并完成’)”); // *后关闭可写流,防止内存泄漏
  }
  const currentFile = path.resolve(__dirname, ‘scripts/’, scripts.shift());
  const currentReadStream = fs.createReadStream(currentFile); // 获取当前的可读流
  currentReadStream.pipe(fileWriteStream, { end: false });
  currentReadStream.on(‘end’, function() {
    streamMergeRecursive(scripts, fileWriteStream);
  });
  currentReadStream.on(‘error’, function(error) { // 监听错误事件,关闭可写流,防止内存泄漏
    console.error(error);
    fileWriteStream.close();
  });
}
streamMerge(‘./scripts’, ‘./script.js’);

如何应对分布式任务的监控

如何应对分布式任务的监控
现在越来越多的组件都是集群化,任务化。我们接下来谈谈任务化的挑战和解决方案。
分布式任务的挑战
以前的进程都是常驻进程。ip都是固定的,如果想知道运行状态,可以说方案就很多很多。*简单的直接用jconsole去连接,jstat去看看。这个都是可行的方案。对于分布式任务,如何找出运行的ip就成了一个问题。一般的开源程序,都会带着管理界面,就是通过自带的任务界面去查看现在是在哪个机器上运行,然后再当做单独的进程进行管理。这个在给诊断调优上带来了很大麻烦,可能大家感觉这个似乎是没怎么麻烦,不就是多了一个步骤获取ip吗。下面我们开始按照生产的标准来操作一下。
客户发现程序运行没有满足条件。找运维开发。
运维开发根据客户描述,找到了分布式任务,一看任务分布在3个机器上。
然后根据任务的执行情况,运行状态等找出了可能有问题的2个。
再去申请机器的登录权限吧,毕竟机器的权限是个重要的点,赶紧申请。
申请审批下来,进了机器,发现,咦进程已经被管理程序发现异常,杀掉了,任务转移到其他机器了。
又开始*步。运气好的话,这还是个死循环。
生成系统上的权限控制各位严格,不像自己搭建做demo,可以很快的用root等高级权限去登录机器。这个时间周期会发生很多情况了。我们再看另外一个情况。
客户发现程序运行没有满足条件。找运维开发。
运维开发一看,这是跑在一个大集群上的分布式任务啊,现在有6000台机器都在运行这个分布式任务。
然后一个一个找吧,看看哪个运行很慢,哪个指标不太正常,仔细看看,这个不太正常,但是也不确定是错误的,先记录下来,然后呢,再往后看。
等6000看完了,任务都执行完了,刚才的情况已经无法再获取更详细的信息了。
生产集群规模的数量远超我们的想象,数据太多了筛选成了一个大难题。
解决问题
上面描述了分布式任务的难题,我们尝试解决一下问题。任务失败重试等问题,我们利用主动标识的方式解决。想在1000个里找你要的,如果没有规律就很麻烦,不如改成,你喊名字,那个人出列。每个进程可以上报自己的数据,带上自己的pid,暴露的信息,任务信息等。这样就可以准确的获取到当时进程跑的数据了。我们不知道ip但是可以根据任务名称或者任务的id,找出5个ip的数据,可以看到暴露的指标。5个确实容易看,这个不解决上千力度的问题。对于这种筛选,我们应该分两个维度,首先是集群维度,就是要找出问题点。这种数据,往往不需要几千个一起看,都是选择top10,top5来查看,毕竟是找问题,那么问题的表现一般都是伴随着特别高,特别低等。这个其实也是我们常见的一个筛选思路。找出top之后,然后切换视角到单个进程,查看单个进程的详细数据。数量量就从上千到单个了。如何针对程序迁移呢,例如原来在ip1上跑,出了问题,发现也慢了,也被切换到ip2这里怎么解决。进程某一个时间点上,他肯定是跑在一台机器上的。根据这个问题,我们可以依靠时序数据库他本身的一些功能,例如找出1小时内的一个top,这个必须是有时间线索的,或者说我们持续关注或者增加告警,等下次出现,快速响应。
部署
解决方式其实就是主动上报数据带着足够的信息,这样容易及时发现。因为这个都是动态的,那部署如何去暴露指标是一个难题。我们常见2种方式。
自己专属暴露
这里其实就是依靠程序自己编写自己的暴露内容,当自己的程序启动时,就可以把指标吐在一个固定的地方,等待被抓取。
公众服务
例如java程序可以利用javaagent,把agent放在一个共享存储上。然后启动的时候加载共享存储的数据,这样不论你跑在哪个机器上,都可以获取到javaagent。通过一些约定的方式,例如启动参数,或者是环境变量等等,获取到任务的id等等。
挑战
上面已经解决了任务飘动以及快速查看错误的方式。但这些还不够,*关键的部分是如何从集群角度分析数据。可能简单的top规则已经不符合需求了,这个就需要业务经验来支撑。如何构建一套规则引擎,这个成了分析的*大挑战。如何找出有问题的数据的方式,我们常见的有*简单的阈值,箱线图,正态分布的标准差。如何更快速的发现问题,这个是在解决基本不可达问题之后,新的挑战。

妥了!微服务治理的困难,用 Serverless 来解决

微服务治理面临的挑战

在业务初期,因人手有限,想要快速开发并上线产品,很多团队使用单体的架构来开发。但是随着公司的发展,会不断往系统里面添加新的业务功能,系统越来越庞大,需求不断增加,越来越多的人也会加入到开发团队,代码库也会增速的膨胀,慢慢的单体应用变得越来越臃肿,可维护性和灵活性逐渐降低,维护成本越来越高。

%title插图%num

这个时候很多团队会把单体应用架构改为微服务的架构,解决单体应用的问题。但随着微服务越来越多,运维投入会越来越大,需要保证几十甚至几百个服务正常运行与协作,这给运维带来了很大的挑战,下面从软件生命周期的角度来分析这些挑战:

  • 开发测试态
    • 如何实现开发、测试、线上环境隔离?
    • 如何快速调试本地变更?
    • 如何快速部署本地变更?
  • 发布态
    • 如何设计服务发布策略?
    • 如何无损下线旧版本服务?
    • 如何实现对新版本服务灰 度测试?
  • 运行态
    • 线上问题如何排查?有什么工具可以利用呢?
    • 对于服务质量差的节点如何处理?
    • 对于完全不工作的实例我们如何恢复?

面对以上问题,Serverless 应用引擎在这方面都做了哪些工作?

%title插图%num

Serverless 应用引擎

%title插图%num

如上图所示,Serverless 应用引擎(SAE)基于神龙 + ECI + VPC + SLB + NAS 等 IaaS 资源,构建了一个 Kubernetes 集群,在此之上提供了应用管理和微服务治理的一些能力。它可以针对不同应用类型进行托管,比如 Spring Cloud 应用、Dubbo 应用、HSF 应用、Web 应用和多语言应用。并且支持 Cloudtoolkit 插件、云效 RDC / Jenkins 等开发者工具。在 Serverless 应用引擎上,零代码改造就可以把 Java 微服务的应用迁移到 Serverless。

总的来说,Serverless 应用引擎能够提供成本更优、效率更高的一站式应用托管方案,零门槛、零改造、零容器基础,即可享受 Serverless + K8s + 微服务带来的技术红利。

%title插图%num

微服务治理实践

1. 开发态实践

1)多环境管理

%title插图%num

  • 多租户共有一个注册中心,通过不同的租户对流量进行隔离;更进一步可以通过网络 VPC 进行环境隔离;
  • 提供环境级别的运维操作,比如一键停止和拉起整个环境的功能;
  • 提供环境级别的配置管理;
  • 提供环境级别的网关路由流量管理。

2)云端联调

Serverless 应用引擎(SAE)基于 Alibaba CloudToolkit 插件+ 跳板机可以实现:

  • 本地服务订阅并注册到云端 SAE 内置的注册中心;
  • 本地服务可以和云端 SAE 服务互相调用。

%title插图%num

如上图所示,在实现的时候用户需要有一个 ECS 代理服务器,实际注册的是 ECS 代理服务器到 SAE 的注册中心,IDEA 在安装 Cloudtoolkit 插件以后,在启动进程时,会在本地拉起一个通道服务,这个通道服务会连上 ECS 代理服务器,本地所有的请求都会转到 ECS 代理服务器上,云端对服务的调用也会通过 ECS 代理转到本地,这样就可以以*新的代码在本地断点调试,这就是云端联调的实现。

3)构建快速开发体系

%title插图%num

代码在本地完成联调以后,要能快速地通过 Maven 插件和 IDEA-plugin,可以很快地一键部署到云端的开发环境。

2. 发布态实践

1)应用发布三板斧

%title插图%num

%title插图%num

  • 可灰度:应用在发布的过程中,运维平台一定要有发布策略,包括单批、分批、金丝雀等发布策略;同时还要支持流量的灰度;批次间也要允许自动/手动任选。
  • 可观测:发布过程可监控,白屏化实时查看发布的日志和结果,及时定位问题。
  • 可回滚:允许人工介入控制发布流程:异常中止、一键回滚。

通过这三点可以让应用发布做到可灰度、可观测、可回滚。

2)微服务无损下线

在版本更换的过程中,SAE 是如何保证旧版本的微服务流量可以无损地下线掉?

%title插图%num

上图是微服务注册和发行的整个流程,图中有服务消费者和服务提供者,服务提供者分别有 B1、B2 两台实例,服务消费者分别有 A1、A2 两台实例。

B1、B2 把自己注册到注册中心,消费者从注册中心刷新服务列表,发现服务提供者 B1、B2,正常情况下,消费者开始调用 B1 或者 B2,服务提供者 B 需要发布新版本,先对其中一个节点进行操作,如 B1,首先停止 Java 进程,服务停止过程又分为主动销毁和被动销毁,主动销毁是准实时的,被动销毁的时间由不同的注册中心决定,*差的情况可能需要一分钟。

如果应用是正常停止,Spring Cloud 和 Dubbo 框架的 ShutdownHook 能正常被执行,这一步的耗时基本上是可以忽略不计的。如果应用是非正常停止,比如说直接 Kill-9 的一个停止,或者是 Docker 镜像构建的时候,Java 进程不是一号进程,且没有把 Kill 信号传递给应用的话,那么服务提供者不会主动去注销节点,它会等待注册中心去发现、被动地去感知服务下线的过程。

当微服务注册中心感知到服务下线以后,会通知服务消费者其中一个服务节点已下线,这里有两种方式:注册中心的推送和消费者的轮巡。注册中心刷新服务列表,感知到提供者已经下线一个节点,这一步对于 Dubbo 框架来说不存在,但对于 Spring Cloud 来说,它*差的刷新时间是 30 秒。等消费者的服务列表更新以后,就不再调用下线节点 B。从第 2 步到第 6 步的过程中,注册中心如果是 Eureka,*差的情况需要消耗两分钟;如果是 Nacos,*差的情况需要消耗 50 秒。

在这个时间内请求都有可能出现问题,所以发布的时候会出现各种报错。

%title插图%num

经过上面的分析,在传统的发布流程中,客户端有一个服务端调用报错期,这是由于客户端没有及时感知到服务端下线的实例造成的,这种情况主要是因为服务提供者借助微服务,通知消费者来更新服务提供的列表造成的。

%title插图%num

那能否绕过注册中心,服务提供者直接通知服务消费者?答案是肯定的。SAE 做了两件事情,*,服务提供者在应用发布前,会主动向服务注册中心注销应用,并将应用标记为已下线状态,将原来停止进程阶段的注销变成了 preStop 阶段注销进程。

在接收到服务消费者的请求时,首先会正常处理本次请求,并且通知服务消费者此节点已经下线,在此之后消费者收到通知后,会立即刷新自己的服务列表,在此之后服务消费者就不会再把请求发到服务提供者 B1 的实例上。

通过上面这个方案,就使得下线感知时间大大缩短,从原来的分钟级别做到准实时的,确保你的应用在下线时能够做到业务无损。

3)基于标签的灰度发布

%title插图%num

发布策略分为分批发布和灰度发布,如何实现流量的灰度?从上面的架构图中可以看到,在应用发布之前,要配置一个灰度规则,比如按 uid 的取模余值 =20 来作为灰度流量的规则,当应用发布的时候,会对已发布的节点标识为一个灰度的版本,在这样的情况下,当有流量进来时,微服务网关和消费者都会通过配置中心拿到在治理中心配置的灰度规则。

消费者的 Agent 也会从注册中心拉取它所依赖的服务的一些信息,当一个流量进到消费者时,会按照灰度规则来做匹配,如果是灰度的流量,它会转化到灰度的机器上;如果是正常流量,它会转到正常的机器上,这是基于标签实现的灰度发布的具体逻辑。

3. 运行态实践

1)强大的应用监控 & 诊断能力

%title插图%num

运行态的实例,服务的运行过程中会出现这样或者那样的问题,怎么去排查和解决它?

排查和解决的前提是必须具有强大的应用监控能力和诊断能力,SAE 集成了云产品 ARMS,能够让跑在上面的 Java 微服务看到应用的调用关系拓扑图,可以定位到你的 MySQL 慢服务方法的调用堆栈,进而定位到代码级别的问题。

比如一个请求响应慢,业务出现问题,它可以定位到是哪个请求、哪个服务、服务的哪行代码出现了问题,这样就能为解决问题带来很多便利。总的来说,就是我们要先有监控报警的能力,才能帮助我们更好地诊断服务运营过程中的问题。

2)故障隔离和服务恢复

上面说到我们通过监控、报警来排查、解决遇到的问题,那我们的系统能否主动去做一些事情呢?SAE 作为一个 Serverless 平台,具备很多自运维的能力,下图中有两个场景:

%title插图%num

  • 场景1:某应用运营过程中,某几台机器由于磁盘满或者宿主机资源争抢,导致 load 很高或网络状态差,客户端出现调用超时或者报错。

面对这种情况,SAE 提供了服务治理能力,即离群摘除,它可以配置,当网络超时严重或者后端服务 5xx 报错达到一定比例时,可以选择把该节点从消费端服务列表中摘除,从而使得有问题的机器不再响应业务的请求,很好地保证业务的 SLA。

  • 场景2:某应用运行过程中,因突发流量导致内存耗尽,触发 OOM。

这种情况下,通过 SAE 这种 Serverless 应用引擎,节点在配置健康检查以后,节点里的容器是可以重新拉起的,可以做到快速对进程进行恢复。

3)精准容量+限流降级+*致弹性

%title插图%num

基于 Serverless Paas 平台 SAE 和其他产品的互动,来达到整个运维态的闭环。

用户在使用的时候,可以运用 PTS 压测工具构造场景,然后得出来一些阈值。比如可以对流量高峰所需要消耗的资源进行预估,这时就可以根据这些阈值设计弹性策略。当业务系统达到请求比例时,就可以按照所设置的弹性策略来扩缩容自己的机器。

扩缩容在时间上,有可能还跟不上处理大批量的请求,这时可以通过和 AHAS 的互动,配置限流降级的能力。当有突发大流量时,首先可以用 AHAS 的能力把一些流量挡在门外,然后同时触发 SAE 上应用的扩容策略去扩容实例,当这些实例扩容完成之后,整个机器的平均负载会下降,流量又重新放进来。从突发大流量到限流降级再到扩容,*后到流量达到正常状态,这就是“精准容量+限流降级+*致弹性”的*佳实践模型。

%title插图%num

总结

本文首先按照提出问题、解决问题的思路,阐述微服务在开发、发布和运行态是如何解决问题的;再介绍如何通过 Serverless 产品和其他产品的互动,从而实现精准流量、限流降级和*致弹性。

  • 开发测试态
    • 通过注册中心多租户和网络环境的隔离,并提供环境级别的能力;
    • 通过云端联调技术来快速调式本地变更;
    • 如果 IDE 插件快速部署本地变更。
  • 发布态
    • 运维平台针对应用发布需要具备可灰度、可观测、 可回滚;
    • 通过 MSE agent 能力实现服务无损下线;
    • 通过标签路由提供了线上流量灰度测试的能力。
  • 运行态
    • 建立强大应用监控和诊断能力;
    • 对服务质量差的节点具备离群摘除能力;
    • 对已经不工作的实例通过配置健康检查能够做到实例重启恢复业务;
    • 提供了精准容量+限流降级+*致弹性模型。

上手 Docker 容器,不应该是个问题

在微服务时代,服务数量及规模越来越大,服务的部署及运维的模式如果仍然采用传统方式就会大大增加运维成本。所以微服务时代的运维方式一定是Devops模式,通过构建自动化运维发布平台来打通产品、开发、测试及运维流程,从而整体上提升研发效能,而这也是目前大部分公司正在做的事情。

随着以Docker为代表的容器化技术的普及,目前Devops实践大多会采用容器(如Docker、K8s)这样的方式来作为微服务应用部署运行的载体,并通过容器的弹性扩展来实现快速扩容和缩容,从而更快地响应业务、更好地利用资源。

目前Devops*流行的部署方案是基于K8s的集群方案,但是它本身也是基于Docker容器技术的,所以在接触K8s技术之前,先通过本文了解下Docker及基于Docker的容器化部署。

%title插图%num

Docker的基本概念

Docker是一个开源的应用容器引擎,也是目前*流程的应用部署方式,通过它可以把应用及其依赖打包到一个可移植的镜像中,然后利用Docker提供的部署机制将其发布至任意安装了Docker容器的系统环境中。从使用角度主要需要理解一下几个要点如图所示:

%title插图%num

如上图所示,理解Docker的使用方式需要掌握以下几个概念:

  • Image(镜像):它是一个可执行文件,包含应用代码、依赖库、运行环境(如JRE等)以及环境变量及配置等信息,通过镜像可以启动一个应用,镜像的构建过程通过Dockefile文件描述。
  • Container(容器):使用Image启动的一个进程实例,它与镜像之间为一对多的关系,一个镜像可以启动多个容器实例。
  • Service(服务):一组提供对外服务的Container,这些Container使用同一个Image镜像,它与镜像为一对一、与容器为一对多的关系,Service由docker-compose文件定义。
  • Stack(应用):一组Service,相互协作对外提供服务,可以看作是一个完整的应用,在一些复杂的场景中会拆分为多个Stack,由docker-compose构建。

%title插图%num

Docker部署一个Spring Boot服务

为了更进一步加深对上述概念的理解,这里以一个Spring Boot应用为例演示如何通过Docker部署一个Spring Boot服务。这里可以通过IDE创建一个简单的Spring Boot应用并写一个测试接口,如下图所示:

%title插图%num

以上为通过IDEA创建的一个*为简单的Spring Boot应用程序,运行后启动服务可以通过端口访问测试接口,接下来使用Docker部署该服务,步骤如下:

  创建Dockerfile文件构建Docker镜像

按照前面Docker的介绍,如果要让Spring Boot程序运行在Docker容器上,首先需要构建Docker镜像,而构建的过程则需要通过Dockerfile文件来描述。例如在项目src/main/docker目录创建Dockerfile文件,代码如下:

  1. 1FROM java:8
  2. 2VOLUME /tmp
  3. 3RUN mkdir /app
  4. 4ADD springboot-1.0-SNAPSHOT.jar /app/springboot.jar
  5. 5ADD runboot.sh /app/
  6. 6RUN bash -c ‘touch /app/springboot.jar’
  7. 7WORKDIR /app
  8. 8RUN chmod a+x runboot.sh
  9. 9EXPOSE 9090
  10. 10CMD /app/runboot.sh
上述Dockerfile文件定义了运行的基础信息为JDK1.8、容器运行的目录为/app、并添加了所需的Jar包等信息,*后定义了要执行的命令为“/app/runboot.sh”脚本。runboot.sh脚本代码如下:
  1. 1sleep 10
  2. 2java -Djava.security.egd=file:/dev/./urandom -jar  /app/springboot.jar

这里打包Spring Boot应用Docker镜像的Dockerfile文件就定义好了,为了能在Maven项目中执行Docker镜像构建命令,还需要在项目pom.xml文件添加Maven Build插件信息,代码如下:

  1. 1<!–Docker Maven插件依赖–>
  2. 2<plugin>
  3. 3    <groupId>com.spotify</groupId>
  4. 4    <artifactId>docker-maven-plugin</artifactId>
  5. 5    <configuration>
  6. 6        <imageName>${project.name}:${project.version}</imageName>
  7. 7        <dockerDirectory>${project.basedir}/src/main/docker</dockerDirectory>
  8. 8        <skipDockerBuild>false</skipDockerBuild>
  9. 9        <resources>
  10. 10            <resource>
  11. 11                <directory>${project.build.directory}</directory>
  12. 12                <include>${project.build.finalName}.jar</include>
  13. 13            </resource>
  14. 14        </resources>
  15. 15    </configuration>
  16. 16</plugin>

接下来可以通过Maven命令构建Spring Boot应用Docker镜像,命令如下:

  1. 1mvn clean package docker:build

运行成功可以看到本地Docker仓库中镜像信息,命令如下:

%title插图%num

这表示Spring Boot程序的Docker镜像已打好,需要说明的是以上命令运行是需要你的系统已经安装Docker容器运行环境。

  创建docker-compose.yml文件

有了Docker镜像,如何将镜像作为容器启动以及该镜像中启动那些服务、它的资源限制及网络使用什么方式,这些都是docker-compose文件定义的,其代码如下:

  1. 1version: ‘3.2’
  2. 2services:
  3. 3  springboot:
  4. 4    image: springboot:1.0-SNAPSHOT
  5. 5    hostname: springboot
  6. 6    environment:
  7. 7      – SPRING_PROFILES_ACTIVE=${SPRING_PROFILES_ACTIVE:-debug}
  8. 8    ports:
  9. 9      – “9999:9090”
  10. 10    networks:
  11. 11      – mynet
  12. 12networks:
  13. 13  mynet:
  14. 14    external: true

在上述docker compose文件中定义了一个springboot服务,然后针对该服务描述了其所使用的Docker镜像、环境变量参数、容器端口映射及网络等信息。需要说明的是services下面还可以定义服务,stack(应用)与service(服务)的关系在docker-compose中是一对多的关系,只是这里暂时没有需要定义其他服务。

启动Docker容器实现应用容器部署

通过上述准备,此时就可以通过docker-compose启动Spring Boot应用的Docker镜像,目录切换到src/main/docker目录,执行如下命令:

  1. 1$ docker-compose up -d
  2. 2Creating docker_springboot_1 … done

此时应用就已经通过Docker容器部署了,可以通过如下命令进行查看:

  1. 1$ docker ps
  2. 2CONTAINER ID        IMAGE                     COMMAND                  CREATED             STATUS              PORTS                                                                      NAMES
  3. 34117e4a8963e        springboot:1.0-SNAPSHOT   “/bin/sh -c /app/run…”   5 seconds ago       Up 3 seconds        9090/tcp, 0.0.0.0:9999->9999/tcp                                           docker_springboot_1

到这里就大功告成了,访问9999端口就能够访问到Docker容器中的Spring Boot服务了。

Docker私有镜像仓库是什么?

Docker镜像仓库概述

镜像仓库作为Docker技术的核心组件之一,其主要作用就是负责镜像内容的存储和分发。Docker镜像仓库从使用范围来说分为“公有镜像仓库”和“私有镜像仓库”,公有镜像仓库是可以被任何人使用的,例如Docker公司维护的在线存储库Docker Hub以及部分云服务厂商(如阿里云)提供的在线Docker镜像库等,都属于公有镜像仓库的范畴。

而私有镜像仓库则是指部署在公司或组织内部,用于自身应用Docker镜像存储、分发的镜像仓库。在构建公司内部使用的自动化发布系统的过程中,从安全的角度出发,应用的打包镜像一般情况下只会被存储在私有镜像仓库中,CI/CD流程的衔接点也是通过向私有镜像仓库上传镜像和拉取镜像的操作来完成的。

%title插图%num

在现阶段主流的企业级私有镜像仓库构建方案中,比较流行的是:开源的企业级Docker镜像仓库——Harbor、以及商业镜像仓库——JFrog Artifactory

这两种Docker镜像仓库各自都有一定的市场,就作者所工作过的公司来说使用Harbor和JFrog Artifactory作为私有镜像仓库的都有,但就成熟度和功能性完整性来说JFrog Artifactory作为商业级解决方案会更具优势,所以目前国内有钱的互联网公司选择JFrog Artifactory作为企业级私有仓库的比较多,本文的主要内容是演示如何通过Docker的方式来快速部署JFrog Artifactory并将其作为Devops自动发布系统的私有镜像仓库。

%title插图%num

JFrog Artifactory镜像仓库部署

根据官方介绍JFrogArtifactory是目前全球唯一一个支持所有开发语言,任意维度的元数据检索、跨语言正反向解析,并拥有深度递归、支持多活异地灾备的企业级、高可用二进制制品管理仓库。这里的二进制制品是指构建过程的输出物,包括软件包、测试报告,应用配置文件等可在服务器上直接运行或可查看的二进制软件制品。

JFrog Artifactory支持多种仓库类型,除了Docker镜像仓库外还支持Maven、Npm等其他类型的仓库。在正式企业级环境中镜像仓库的部署要考虑高可用、扩展性等要求,关于这方面的部署方式可以参考其他官方文档,本环节为了方便演示将采用Docker的方式进行部署,具体步骤如下:

1)、获取*新JFrog Artifactory社区版Docker镜像,命令如下:

$ docker pulldocker.bintray.io/jfrog/artifactory-jcr:latest

由于网络原因上述下载过程可能会比较慢,有条件的读者可以借助科学上网方式进行镜像获取。完成后可通过命令查看具体的镜像信息,如下:

$ docker images

2)、创建数据卷。考虑到镜像仓库作为持久化存储服务,这里为其创建单独的数据卷,命令如下:

  1. #创建数据目录
  2. $ mkdir -p ~/docker/volume/artifactory
  3. #切换到上述目录后执行数据卷创建命令
  4. $ docker volume create data_artifactory

3)、运行Docker容器,命令如下:

$ docker run --name jfrog-artifactory -d-v data_artifactory:/var/opt/jfrog/artifactory -p 8081:8081 -p 8082:8082docker.bintray.io/jfrog/artifactory-jcr:latest

完成上述操作后,如果容器运行成功,此时浏览器输入访问地址:http://127.0.0.1:8082,将会显示如图所示界面:

%title插图%num

输入初始密码(admin/password)后,会继续跳转到管理员密码重置界面,如图所示:

%title插图%num

依据步骤设置好管理员密码。之后会继续要求设置如订阅邮箱、访问网站域名、代理配置等信息,这里可以暂时忽略直接跳到仓库创建的页面,如图所示:

%title插图%num

从上图可以看到JFrogArtifactory支持多种类型的仓库,例如Maven私有仓库也可以通过JFrog Artifactory来进行配置,由于是社区版所以很多其他类型的仓库并未免费开放!这里我们只选择创建Docker镜像仓库,创建后效果如图所示:

%title插图%num

如上图所示,默认创建了一个RepositoryKey为docker-local的本地仓库。在实际工作场景中为了便于Docker镜像的管理,可以分别为同一代码空间的项目创建单独的镜像仓库,例如我们本书中所有的Spring Cloud实战项目创建一个单独镜像仓库,点击右上角“New LocalRepository”,如图所示:

%title插图%num

到这里JFrog Repository容器镜像仓库的基本部署工作就完成了,后面自动化发布系统中的CI/CD流程将以此为基础实现Docker镜像的存储、分发!关注我后续的内容将向你介绍如何构建一套完整的CI/CD自动发布系统!

使用阿里云服务器遇到的一些问题及解决办法

系统环境:centos_7

1. *近在ESC实例上部署了jdk和tomcat,正确配置并启动tomcat后,外网无法访问。

解决办法:在网上查了很多资料后,发现阿里云有“经典网络”和“专有网络”2种。专有网络默认没有开放8080、80、25、3306等一系列的端口。而我使用的服务器的网络类型就是专有网络。

%title插图%num

因此只需手动设置开放需要使用的端口即可,依次点击:控制台->云服务器ECS->网络和安全->安全组->配置规则->添加安全组规则,如下图所示。

%title插图%num

至此,外网已经可以访问8080端口,其他端口也可以按照上述步骤设置。也可以通过点击“快速创建规则”一次性开放多个常用端口,如下图所示。

%title插图%num

如果外网仍然无法访问,可能是服务器的防火墙关闭了某些端口。CentOS 7可以使用firewall打开或关闭防火墙端口。

查看某个端口的状态:

firewall-cmd –zone=public –query-port=8080/tcp
显示no则表示已关闭,可以通过以下命令打开:

firewall-cmd –zone=public –add-port=8080/tcp –permanent (–permanent永久生效,没有此参数重启后失效)

重新载入:

firewall-cmd –reload

再次查看该端口的状态,显示yes,说明已经成功打开。

完成以上配置之后,外网已经可以成功访问了。

(使用svn需要按照上述步骤开放3690端口、mysql需要开放3306端口、邮件服务需要开放25端口…)

一文看懂5G射频的“黑科技”

手机,作为移动互联网时代的标配,已经走进了我们每个人的生活。有了它,我们可以随心所欲地聊天、购物、追剧,享受美好的人生。

正因为手机如此重要,所以人们对相关技术的发展十分关注。每当有新品发布,媒体会进行长篇累牍的报道,社交网络上也会掀起热烈的讨论。

然而,人们对手机的关注,往往集中在CPU、GPU、基带、屏幕、摄像头上。有那么一个特殊的部件,对手机来说*为重要,却很少有人留意。

是哪个部件呢?没错,它就是我们今天文章的主角——射频。

%title插图%num

%title插图%num

什么是射频

射频,英文名是Radio Frequency,也就是大家熟悉的RF。从字面上来说,Radio Frequency是无线电频率的意思。射频信号,则特指频率范围在300KHz~300GHz的无线电磁波。

大家都知道,手机之所以能够和基站进行通信,靠的就是互相收发无线电磁波。

%title插图%num

手机里专门负责收发无线电磁波的一系列电路、芯片、元器件等,被统称为射频系统,简称“射频”(下同)。

射频和基带,是手机实现通信功能的基石。如果我们把手机与外界的通信看作是一项“快递服务”,那么,基带的职责是对数据进行“打包/拆包”。而射频的职责,则是将“包裹”通过指定的无线电频段发射出去/接收下来。

%title插图%num

示意图:左边是基带,右边是射频

射频到底长什么样?下面这张,是某品牌手机的主电路板正反面布局图。

%title插图%num

%title插图%num

(图片来自ABI Research)

图中,黄色圈出的部分,全部属于射频。可以看出,射频元件在手机构造中,占据了不小的比例。

从架构上来看,一套完整的射频系统包括射频收发器、射频前端、天线三个部分。射频前端又包括功率放大器、包络追踪器、低噪声放大器、滤波器、天线开关、天线调谐器等多个组件。

%title插图%num

射频的架构

射频前端各个组件的作用并不复杂。例如,放大器,就是把信号放大,让信号传得更远;滤波器,是把杂波去掉,让信号更 “纯净”;天线开关,用于控制天线的启用与关闭;天线调谐器,主要作用是“摆弄”天线,获得*好的收发效果……

%title插图%num

数量众多的射频组件,相互配合,分工协作,就是为了完成“临门一脚”,把基带打包好的数据,“biu~biu~biu~”地发射出去。

%title插图%num

如果射频设计不合理,元器件性能落后,那么,将直接影响手机的无线信号收发能力,进而影响手机的通信能力。具体表现出来,就是无线信号差,通信距离短,网络速率慢,等等。

换言之,手机的射频能力不行,就好比汽车的动力不足,就算其它功能再花哨,也无法被用户所接受。

所以,手机厂商在研发设计手机时,通常都会在射频方面下足功夫,反复推敲并进行测试验证,才敢推出*终产品。

%title插图%num

5G射频的挑战

如今,我们昂首迈入了5G时代。相比传统4G,5G的射频系统有变化吗?

答案是肯定的。不仅有变化,而且是巨变。

5G相比4G,在性能指标上有了大幅的提升。5G的eMBB(增强型移动宽带)场景,将手机速率提升至千兆级甚至万兆级,分别是早期LTE速率(100Mbps)的10倍/100倍。

2G/3G/4G,加上5G,加上MIMO(多天线技术),加上双卡双待,手机的天线数量和支持频段翻倍增加。4G早期只有不到20个频段组合。相比之下,5G有超过10000个频段组合,复杂性堪称恐怖。

与此同时,为了确保用户愿意升(tāo)级(qián),5G手机的厚度和重量不能增加,功耗不能增加,待机时长不能减少。

换做你是手机厂商,你会不会抓狂?

所以说,5G手机的射频,必须重塑自我,大力出奇迹搞创新。

到底该如何解决射频系统的设计难题呢?高通提出了一个宏观的思路,直接提供“完整的调制解调器及射频系统”。通俗理解,就是把基带、射频收发器、射频前端、天线模组、软件框架等,全部都做好,给厂商一套完整的方案。

也就是说,5G手机等终端元器件设计的理念,必须摒弃以往“东市买骏马,西市买鞍鞯,南市买辔头,北市买长鞭”专注于单个元件的思路,转而采用“打包设计”的一体化系统级解决方案。

例如,以前是A厂造基带,B厂造射频,C厂造天线,然后手机D厂自己捣鼓如何整合和对接。现在,改成有实力的大厂直接把基带、射频和天线等一起打包设计好,然后交给手机厂商,拿了就能快速使用。

%title插图%num

系统级集成,是5G基带和射频复杂度大幅提升的必然结果。

这就好比是火车。以前绿皮车的车速慢,车厢和车头可以分开设计、制造,然后拼在一起运行。但是,到了高铁时代,速度指标翻倍,如果继续分开设计、制造,车厢和车头不能深度协同,不仅速度指标难以实现,还可能出现安全问题。

所以,高铁的动车组,通常都是统一设计和制造的。

也就是说,面对前面提及的苛刻5G指标,需要站在系统级集成的角度,对基带和射频进行整体设计。这样一来,才能让两者实现完美的软硬件协同,发挥*佳性能(吞吐率、覆盖范围等)。

除了达成指标之外,整合设计也有利于缩减系统的*终尺寸,减少对手机空间的占用。对于系统功耗和散热控制来说,整合设计也有明显优势。

*后一点,也是很重要的一点,提供系统级整合方案,可以降低手机厂商的设计难度,方便他们以更快的速度推出产品,抢占市场。

%title插图%num

5G射频的黑科技

我们来具体看看,系统级集成的5G射频,到底有哪些有趣的黑科技。

首先,*个黑科技,就是宽带包络追踪。

前面介绍射频架构的时候,里面就有一个功率追踪器。功率追踪器是配合功率放大器使用的。

功率放大器是射频的核心元件,它就像一个喇叭,把小声音(信号)变成大声音(信号)。

想要把喇叭吹响,肯定需要鼓足力气(电源供电)。功率追踪器的作用,就是控制吹喇叭的力度(功率)。

%title插图%num

传统的吹法,是APT法,也就是平均功率追踪。某一时间段内,吹的力量保持不变。

而宽带包络追踪(ET)技术,可以精确地控制力量。也就是说,基带(调制解调器)可以根据信号的变化,控制射频里的包络追踪器,进而精准控制无线信号的发射功率。

%title插图%num

包络追踪的虚耗电量明显小于传统平均功率追踪

(图片来自高通)

这样一来,体力(能量)大幅节约了,射频的功耗也就下降了,手机的待机时间得以增加。

精准的发射功率控制,帮助手机获得*佳的信号发射效率,从而获得更好的信道质量。在手机与基站“双向沟通”过程中,当手机获得更好的信道质量时,基站就能支持手机实现更优的上下行业务,例如支持2×2 MIMO,网速更加丝滑。此外,更好的信道质量,也为基站侧给手机分配更高阶的调制方式(例如256QAM)创造了条件,可以提升手机吞吐率,支持更快更优的数据传输业务。

高通此前发布的几代骁龙5G调制解调器及射频系统集成的宽带包络追踪器,就已经采用了上述技术。而其*新的宽带包络追踪器QET7100,与目前市场上其它厂商提供的*先进产品相比,能效提升了30%。

我们介绍的第二个黑科技,就是AI辅助信号增强技术。

这个技术是2月份刚推出的骁龙X65 5G调制解调器及射频系统中*新发布的新技术,也是行业里首次将大热的AI技术引入手机射频系统,用于增强信号。

AI辅助信号增强技术的核心,就是将AI技术引入天线调谐系统。天线调谐分为两种方式,一个是阻抗匹配,另一个是孔径调谐。

%title插图%num

我们先看看阻抗匹配。

所谓阻抗匹配,我们可以理解为是一种“接水管”的工作。

射频系统元件与天线之间对接,就像两根水管对接。当阻抗一致时,就是位置完美对应,这时水流*大,信号的效率*高。如果元件的阻抗发生偏移,那么水管就歪了,水流就小了,一部分水流也浪费了。

%title插图%num

导致阻抗变化的原因很多,例如手的触碰,还有插接数据线、安装手机壳等。即便是不同的持握手势(左手、右手、单手、双手),也会带来不同的阻抗。

%title插图%num

传统的阻抗匹配做法,就是在实验室对各种造成阻抗变化的原因进行测试,找到天线特征值,然后通过调制解调器控制射频元件进行阻抗调节,让接水管尽可能对准送水管。

而AI辅助信号增强技术,就是引入AI算法,对各种阻抗变化原因的天线特征值进行大数据分析和机器学习,实现对阻抗的智能调节,达到*完美的匹配效果。

说白了,就有点像在送水管和接水管之间,安装了一根对接软管,让水流尽可能不浪费。

%title插图%num

AI辅助信号增强,相当于射频和天线间的对接软管

孔径调谐相对来说较为简单,就是调节天线的电长度。

从辐射学的角度来说,天线的完美长度应该是波长的四分之一。现在的手机,因为全网通、双卡双待等原因,移动通信系统的工作频率是动态变化的。例如,有时候工作在2.6GHz,有时候工作在3.5GHz。

工作频率如果变化,意味着*佳波长也变化了。所以,需要对天线进行孔径调谐,调节天线的长度,拉长波峰,以此达到*佳效果。

总而言之,以阻抗匹配和孔径调谐为基础的天线调谐技术,主要作用是克服外部环境对天线信号的影响,对信号进行动态调节,改善用户体验。

%title插图%num

根据实际验证,凭借着AI辅助信号增强技术,系统的情境感知准确性可以提升30%,能够明显降低通话掉线率,提升速率、覆盖和续航。

%title插图%num

结语

5G射频系统的创新黑科技还有很多,例如多载波优化、去耦调谐、多SIM卡增强并发等。这些黑科技全部都是技术创新的成果。它们凝结了工程师们的智慧,也为5G终端的顺利推出奠定了基础。

如今的5G射频,已不再是基带的辅助,而是能够和基带平起平坐、相辅相成的重要手机组件。

随着5G网络建设的不断深入,除了手机通信之外,越来越多的5G垂直行业应用场景也开始落地开花。5G终端的形态将会变得五花八门,更大的考验将会摆在5G射频前端的面前。