SegmentFault Woody的专栏最新的文章
2023-10-07T13:49:26+08:00
https://segmentfault.com/feeds/blogs
https://creativecommons.org/licenses/by-nc-nd/4.0/
Nacos本地缓存配置实践
https://segmentfault.com/a/1190000044280470
2023-10-07T13:49:26+08:00
2023-10-07T13:49:26+08:00
Woody
https://segmentfault.com/u/woodyyan
0
<h2>背景</h2><p>前段时间做了一个项目,由于nacos的不稳定性,导致了生产环境拉取配置失败了,从而影响了生产环境的业务。</p><p>于是团队就做了一个大胆的决定,为了避免因为依赖nacos导致业务的不可用,我们一致决定,在本地做nacos的配置缓存。本篇文章只讨论nacos配置缓存的实践,不涉及注册中心。</p><p>经过几轮测试和验证,最终这个方案落地了,做了实际的故障演练,把nacos断了之后,应用是能正确的拿到缓存的配置。</p><p>下面我们就来看看如何实现的。</p><h2><strong>缓存配置的步骤</strong></h2><p>实现本地缓存配置数据的步骤,我这里做了几个具体步骤的总结:</p><ol><li>首先,有一个Nacos服务器,这个是必须的,并且已经创建了相应的配置。</li><li>其次,在程序中引入Nacos的客户端SDK依赖,这个也是必须的。</li><li>然后使用SDK从Nacos服务器获取配置数据,这个也是必须的。</li><li>重点步骤来了,我的实现方式是,将从nacos服务端获取到的配置数据保存在本地缓存中,可以使用内存、文件或其他缓存机制。</li><li>然后在需要访问配置数据的地方,首先检查本地缓存是否存在数据。如果存在,直接使用缓存数据;如果不存在,再从Nacos服务器获取最新的配置数据。</li><li>最后就是要定期刷新本地缓存,以确保获取到的配置数据是最新的。</li></ol><p>这只是其中一种实现方式,还有主动轮训的方式,这里就不讲了。</p><p>本次实践不涉及强实时要求的配置的更新。</p><h2>测试<strong>代码</strong></h2><p>先用java代码测试一下可行性,试一下在本地缓存配置数据:</p><pre><code>import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.utils.StringUtils;
import java.util.Properties;
import java.util.concurrent.Executor;
public class NacosConfigCacheExample {
private static final String SERVER_ADDR = "localhost:8848";
private static final String GROUP_ID = "DEFAULT_GROUP";
private static final String DATA_ID = "example-config";
private static final String CACHE_FILE_PATH = "/path/to/cache/file";
private static ConfigService configService;
public static void main(String[] args) throws NacosException {
// 创建Nacos配置服务实例
Properties properties = new Properties();
properties.put("serverAddr", SERVER_ADDR);
configService = NacosFactory.createConfigService(properties);
// 从Nacos服务器获取配置数据
String configData = configService.getConfig(DATA_ID, GROUP_ID, 5000);
// 将配置数据保存到本地缓存文件
saveConfigToCache(configData);
// 注册配置变更监听器
configService.addListener(DATA_ID, GROUP_ID, new Listener() {
@Override
public void receiveConfigInfo(String configInfo) {
// 当配置发生变化时,更新本地缓存
saveConfigToCache(configInfo);
}
@Override
public Executor getExecutor() {
return null; // 使用默认的执行器
}
});
// 从本地缓存获取配置数据
String cachedConfigData = readConfigFromCache();
if (StringUtils.isNotBlank(cachedConfigData)) {
// 使用缓存数据
System.out.println("Using cached config data: " + cachedConfigData);
} else {
// 缓存数据为空,从Nacos服务器获取最新的配置数据
String latestConfigData = configService.getConfig(DATA_ID, GROUP_ID, 5000);
System.out.println("Using latest config data: " + latestConfigData);
}
}
private static void saveConfigToCache(String configData) {
// 将配置数据保存到本地缓存文件
// 这里使用了简单的文件存储方式,你可以根据实际需求选择其他缓存机制
try (FileWriter writer = new FileWriter(CACHE_FILE_PATH)) {
writer.write(configData);
} catch (IOException e) {
e.printStackTrace();
}
}
private static String readConfigFromCache() {
// 从本地缓存文件读取配置数据
// 这里使用了简单的文件存储方式,你可以根据实际需求选择其他缓存机制
try (BufferedReader reader = new BufferedReader(new FileReader(CACHE_FILE_PATH))) {
StringBuilder configData = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
configData.append(line);
}
return configData.toString();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
</code></pre><p>跑了一下,是可以成功拿到数据,并缓存到文件中的。同时在缓存数据为空的时候,是可以从nacos读取最新的数据的。</p><p>那么下面就在正式的spring项目中开干。</p><h2><strong>Spring Boot代码DEMO</strong></h2><p>项目是用的Spring Boot,那么在项目中引入nacos这种简单的操作就浅写一下吧。</p><pre><code><dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
</code></pre><p>然后,在配置文件中添加Nacos相关的配置这种简单的操作也浅写一下吧。</p><pre><code>spring:
cloud:
nacos:
config:
server-addr: localhost:8848
group: DEFAULT_GROUP
namespace: your-namespace</code></pre><p>接下来,创建一个配置类,用于获取和缓存配置数据:</p><pre><code>import com.alibaba.nacos.api.config.annotation.NacosConfigListener;
import com.alibaba.nacos.api.config.annotation.NacosValue;
import org.springframework.stereotype.Component;
import java.io.*;
import java.util.HashMap;
import java.util.Map;
@Component
public class ConfigCache {
private static final String CACHE_FILE_PATH = "/path/to/cache/file";
private Map<String, String> configDataMap = new HashMap<>();
public String getConfigData(String dataId) {
String configData = configDataMap.get(dataId);
if (configData == null || configData.isEmpty()) {
configData = readConfigFromCache(dataId);
}
return configData;
}
@NacosConfigListener(dataId = "example-config", groupId = "DEFAULT_GROUP")
public void onConfigUpdate(String config, String dataId) {
configDataMap.put(dataId, config);
saveConfigToCache(config, dataId);
}
private void saveConfigToCache(String configData, String dataId) {
try (BufferedWriter writer = new BufferedWriter(new FileWriter(CACHE_FILE_PATH + dataId))) {
writer.write(configData);
} catch (IOException e) {
e.printStackTrace();
}
}
private String readConfigFromCache(String dataId) {
try (BufferedReader reader = new BufferedReader(new FileReader(CACHE_FILE_PATH + dataId))) {
StringBuilder configData = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
configData.append(line);
}
return configData.toString();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}</code></pre><p>这里配置不多的话,可以用一个map来存储。可以加快获取的速度。</p><p>当map中没有的时候,再去文件中读取缓存的配置。这里展示的是用文件存储的。</p><p>实际的项目中,最终是替换成了redis来存储的。</p><p>文件存储的问题在于是,配置很多的话,会产生很多个文件。如果写一个文件的话,当配置很多的时候IO效率又很慢。</p><p>用map存在内存中的问题是,如果配置很多,会占用很多内存。但好处是它查询很快。</p><p>最后,在业务代码中可以使用ConfigCache类来获取配置数据:</p><pre><code>import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ExampleController {
@Autowired
private ConfigCache configCache;
@GetMapping("/config")
public String getConfigData() {
return configCache.getConfigData("example-config");
}
}</code></pre><h2>总结一下里面的坑</h2><p>实际测试下来,最终发现,不同的项目会用不同的缓存。</p><h3>只用MAP</h3><p>在配置很少的那种服务里,比如只有几个或者十几个,就简单的用map缓存在内存中就可以了。</p><ul><li>但它的问题是,如果是服务本身挂了,那么重启后,这些map中的数据就丢失了。</li><li>另外一个问题是,服务有多个实例的时候,每个实例中缓存的map是不一样的。当正好没有缓存的实例去访问nacos拿数据的时候,nacos正好挂了,那么一样会失败。虽然概率很低。</li></ul><h3>用MAP+文件</h3><p>对于配置多,也不在乎读取效率的服务里,就用MAP+文件。其实我们的项目中最终只有一个服务用了这种模式。</p><ul><li>它的问题在于,配置很多的服务中,随着服务的使用,MAP占用的内存会越来越大。</li><li>用文件存储的问题在于,它有一定的概率会出现IO问题。导致读写文件失败。所以兜底的操作就是最终都去nacos读取。</li><li>另外一个问题在于,写到文件中的配置都是明文的。运维或者开发登陆到机器上就能看到配置的信息。有一定的安全风险。</li></ul><h3>用Redis</h3><p>绝大部分的服务最终都用了这种模式。因为用了redis,所以也不需要用MAP了。经过测试,从MAP中读取,和从Redis读取的时间差可以忽略不计,几乎无感知。</p><ul><li>最明显的缺点肯定就是会增加一个redis,在整体架构上多了一环,那就多了一个不稳定因素。因为redis也有挂掉的可能。</li><li>这个redis到底是独立的只用于配置,还是和其他的缓存的redis合用一个,也是一个纠结的问题。不过实际的决策还是要根据业务来。</li><li>还有一个坑是,如果redis和nacos在同一个可用区,那这个可用区挂掉之后,会导致两边都拿不到数据,一样导致不可用。所以redis和nacos本身的高可用部署也是需要考虑的。</li></ul><h3>通用问题</h3><ul><li>数据一致性问题。缓存中的数据和nacos中的数据可能是不一致的。虽然有监听更新,但在大数据量和频繁读取的场景中,也有可能导致不一致的情况出现。所以这种对一致性要求很高的场景,建议做一些一致性保障的逻辑。</li><li>如果Nacos配置数据量非常大或者数量众多,如果是用的缓存到本地文件的方式,可能会占用大量的存储空间,并且读取和写入大量的配置数据可能会影响应用的性能。所以量大的场景不太建议用文件缓存。</li><li>如果在配置中有一些敏感数据,比如密码、敏感数据等,用缓存的方式都可能增加新的安全风险。比如缓存到文件是会被读取到的,缓存到redis通常都是明文存储的。</li></ul><h2>最后<strong>总结</strong></h2><p>考虑了多种实现方式,最终选了上述的方式。另外一种实现方式是,可以主动轮训。</p><p>但它的问题在于,主动轮训会需要确定好时间间隔,太短可能占用应用的性能,太长可能数据更新不及时等。另外就是轮训也有数据一致性问题。</p><p>还有一种缓存方式,就是把量大的配置做分批缓存。比如每批缓存500个配置。由于和我们的业务不太匹配,就没有尝试这种方式。</p><p>总之,我们做nacos本地缓存是为了避免nacos故障导致的业务不可用。每个企业每个项目每个业务遇到的问题可能不一样,大家应该根据自己的实际场景,来寻找最适合的解决方案。</p>
微服务优雅上下线的实践方法
https://segmentfault.com/a/1190000043819832
2023-05-23T16:20:10+08:00
2023-05-23T16:20:10+08:00
Woody
https://segmentfault.com/u/woodyyan
1
<blockquote>💡 本文介绍了微服务优雅上下线的实践方法,包括适用于 Spring 应用的优雅上下线逻辑,以及使用 Docker 实现无损下线的 demo,以及服务预热。同时,本文还总结了优雅上下线的价值和挑战。</blockquote><h2>前言</h2><p><img src="/img/bVc71Fu" alt="image.png" title="image.png"></p><p>微服务优雅上下线的原理是指在微服务的发布过程中,保证服务的稳定性和可用性,避免因为服务的变更而造成流量的中断或错误。<br>微服务优雅上下线的原理可以从三个角度来考虑:</p><ul><li>服务端的优雅上线,即在服务启动后,等待服务完全就绪后再对外提供服务,或者有一个服务预热的过程。</li><li>服务端的无损下线,即在服务停止前,先从注册中心注销,拒绝新的请求,等待旧的请求处理完毕后再下线服务。</li><li>客户端的容灾策略,即在调用服务时,通过负载均衡、重试、黑名单等机制,选择健康的服务实例,避免调用不可用的服务实例。</li></ul><p>微服务优雅上下线可以提高微服务的稳定性和可靠性,减少发布过程中的风险和损失。</p><h2>优雅上线</h2><p><img src="/img/bVc71Fz" alt="image.png" title="image.png"></p><p>优雅上线,也叫无损上线,或者延迟发布,或者延迟暴露,或者服务预热。<br>优雅上线的目的是为了提高发布的稳定性和可靠性,避免因为应用的变更而造成流量的中断或错误。</p><h3>优雅上线的方法</h3><p>优雅上线的方法有以下几种:</p><ul><li>延迟发布:即延迟暴露应用服务,比如应用需要一些初始化操作后才能对外提供服务,如初始化缓存,数据库连接池等相关资源就位,可以通过配置或代码来实现延迟暴露。</li><li>QoS命令:即通过命令行或HTTP请求来控制应用服务的上线和下线,比如在应用启动时不向注册中心注册服务,而是在服务健康检查完之后再手动注册服务。</li><li>服务注册与发现:即通过注册中心来管理应用服务的状态和路由信息,比如在应用启动时向注册中心注册服务,并监听服务状态变化事件,在应用停止时向注册中心注销服务,并通知其他服务更新路由信息。</li><li>灰度发布:即通过分流策略来控制应用服务的流量分配,比如在发布新版本的应用时,先将部分流量导入到新版本的应用上,观察其运行情况,如果没有问题再逐步增加流量比例,直到全部切换到新版本的应用上。</li></ul><p>上面的方法核心思想都是一个,就是等服务做好了准备再把请求放行过去。</p><h3>优雅上线的实现</h3><p>大部分优雅上线都是通过注册中心和服务治理能力来实现的。<br>对于初始化过流程较长的应用,由于注册通常与应用初始化过程同步进行,因此可能出现应用还未完全初始化就已经被注册到注册中心供外部消费者调用,此时直接调用可能会导致请求报错。<br>所以,通过服务注册与发现来做优雅上线的基本思路是:</p><ul><li>在应用启动时,提供一个健康检查接口,用于反馈服务的状态和可用性。</li><li><p>应用启动后,可以采用下列方法来使新的请求暂时不进入新版的服务实例。</p><ul><li>暂时不向注册中心注册服务。</li><li>隔离服务,有些注册中心支持隔离服务实例,比如北极星。</li><li>将权重配置为0,比如nacos。</li><li>将服务实例的enable改为false,比如nacos。</li><li>让健康检查接口返回不健康的状态。</li></ul></li><li>在新版本的应用实例完成初始化操作后,确保了可用性后,再对应的将上述的方法取消,这样就可以让新的请求被路由到新版本的应用实例上。</li><li>如果需要预热,就让流量进入新版本的应用实例时按比例的一点点增加。</li></ul><p>这样,就可以实现优雅上线的过程,保证请求进来的时候,不会因为新版本的应用实例没有准备好而导致请求失败。</p><h3>优雅上线的代码demo</h3><p>我们以 Spring Cloud 和 Nacos 为例,讲一下如何通过服务注册与发现来做优雅上线的过程。<br>首先,我们需要创建一个 Spring Cloud 项目,并添加 Nacos 的依赖。<br>然后,我们需要在 application.properties 文件中配置 Nacos 的相关信息,如注册中心地址,服务名,分组名等,例如:</p><pre><code>spring.application.name=provider
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
spring.cloud.nacos.discovery.group=DEFAULT_GROUP</code></pre><p>接下来,我们需要在启动类上添加 <code>@EnableDiscoveryClient</code> 注解,表示开启服务注册与发现功能,例如:</p><pre><code>@SpringBootApplication
@EnableDiscoveryClient
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class, args);
}
}</code></pre><p>然后,我们需要创建一个 Controller 类,提供一个简单的接口,用于返回服务的信息,例如:</p><pre><code>@RestController
public class ProviderController {
@Value("${server.port}")
private int port;
@GetMapping("/hello")
public String hello() {
return "Hello, I am provider, port: " + port;
}
}</code></pre><p>最后,如果需要我们可以重写健康检查接口,用于反馈服务的状态和可用性。这里我们需要引入<code>Actuator</code>。</p><pre><code>@Component
public class DatabaseHealthIndicator implements HealthIndicator {
@Override
public Health health() {
if (isDatabaseConnectionOK()) {
return Health.up().build();
} else {
return Health.down().withDetail("Error Code", "DB-001").build();
}
}
private boolean isDatabaseConnectionOK() {
// 检查数据库连接、缓存等
return true;
}
}
</code></pre><p>这样,我们就完成了一个简单的服务提供者应用,并且可以通过 Nacos 来实现服务注册与发现。<br>接下来,我们需要创建一个服务消费者应用,并且也添加 Nacos 的依赖和配置信息。<br>然后,我们需要在启动类上添加 <code>@EnableDiscoveryClient</code> 注解,表示开启服务注册与发现功能,并且使用 RestTemplate 来调用服务提供者的接口,例如:</p><pre><code>@SpringBootApplication
@EnableDiscoveryClient
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
@Bean
@LoadBalanced // 开启负载均衡
public RestTemplate restTemplate() {
return new RestTemplate();
}
@RestController
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/hello")
public String hello() {
// 使用服务名来调用服务提供者的接口
return restTemplate.getForObject("<http://provider/hello>", String.class);
}
}
}
</code></pre><p>这里我们使用了 @LoadBalanced 注解来开启负载均衡功能,并且使用服务名 provider 来调用服务提供者的接口。<br>这样,我们就完成了一个简单的服务消费者应用,并且可以通过 Nacos 来实现服务注册与发现。<br>接下来,我们就可以通过以下步骤来实现优雅上线的过程:</p><ul><li>在发布新版本的服务提供者应用时,先启动新版本的应用实例,但是不向注册中心注册服务,或者让健康检查接口返回不健康的状态,这样就不会有新的请求进入新版本的应用实例。这可以通过配置或代码来实现,例如:</li></ul><pre><code># 不向注册中心注册服务
spring.cloud.nacos.discovery.register-enabled=false</code></pre><pre><code>// 让健康检查接口返回不健康的状态
this.isHealthy = false;</code></pre><ul><li>在新版本的应用实例完成初始化操作后,再向注册中心注册服务,或者让健康检查接口返回健康的状态,这样就可以让新的请求被路由到新版本的应用实例上。这可以通过配置或代码来实现,例如:</li></ul><pre><code># 向注册中心注册服务
spring.cloud.nacos.discovery.register-enabled=true</code></pre><pre><code>// 让健康检查接口返回健康的状态
this.isHealthy = true;</code></pre><p>这样,就可以实现优雅上线的过程,保证正在处理的请求不会被中断,而新的请求会被路由到新版本的应用上。</p><h3>服务预热</h3><p>服务预热是指在服务上线之前,先让服务处于一个运行状态,让其加载必要的资源、建立连接等,以便在服务上线后能够快速响应请求。如下图所示。<br>在流量较大情况下,刚启动的服务直接处理大量请求可能由于应用内部资源初始化不彻底从而出现请求阻塞、报错等问题。此时通过服务预热,在服务刚启动阶段通过小流量帮助服务在处理大量请求前完成初始化,可以帮助发现服务上线后可能存在的问题,例如资源不足、连接数过多等,从而及时进行调整和优化,确保服务的稳定性和可靠性。</p><p><img src="/img/bVc71F8" alt="image.png" title="image.png"></p><h3>Spring Boot实现服务预热</h3><p>我们可以通过使用 Spring Boot Actuator 来实现服务预热。</p><ol><li>添加 Spring Boot Actuator 依赖。</li><li>配置了将所有 <code>Actuator</code> 端点暴露出来,并启用了预热端点。</li></ol><pre><code>management.endpoints.web.exposure.include=*
management.endpoint.warmup.enabled=true</code></pre><ol start="3"><li>这时我们就可以调用warmup接口来实现预热了。默认的接口如下:<a href="https://link.segmentfault.com/?enc=WEqLlHPk9MMHxe9sIIP%2BQw%3D%3D.DA2Yw4Vvy6jQcNd2ttKYYNVZePaNxKmJXYYw5Mu7tVnfrHYqG3fRC38XPZwapLCY" rel="nofollow">http://localhost:8080/actuator/warmup</a></li></ol><p>这里spring的warmup 端点会做以下几件事情:</p><ol><li>加载 Spring 上下文</li><li>初始化连接池</li><li>加载缓存数据</li><li>发送测试请求</li></ol><p>如果我们想自定义预热逻辑,我们也可以通过实现<code>warmup</code>接口来自定义预热的逻辑。代码如下:</p><pre><code>@Component
public class MyWarmup implements Warmup {
@Override
public void warmup() {
// 实现预热逻辑
}
}</code></pre><h2>优雅下线</h2><p><img src="/img/bVc71GF" alt="image.png" title="image.png"></p><p>无损下线、优雅下线都是同一个意思。都是为了避免服务下线的时候由于请求没有处理完导致请求失败的情况。</p><h3>优雅下线的方法</h3><p>无损下线的一些常用的工具或框架有:</p><ul><li>Dubbo-go:支持多种注册中心、负载均衡、容灾策略等,可以实现优雅上下线的设计与实践。</li><li>Spring Cloud:提供了多种组件来实现服务的配置、路由、监控、熔断等,可以通过监听 <code>ContextClosedEvent</code> 事件来实现优雅下线的逻辑。</li><li>Docker:可以通过 <code>docker stop</code> 或 <code>docker kill</code> 命令来停止容器,前者会发送 <code>SIGTERM</code> 信号给容器的 PID1 进程,后者会发送 <code>SIGKILL</code> 信号。如果程序能响应 <code>SIGTERM</code> 信号,就可以实现优雅下线的操作。</li></ul><h3>Spring Cloud优雅下线的原理</h3><p><code>ContextClosedEvent</code> 是 Spring 容器在关闭时发布的一个事件,可以通过实现 ApplicationListener 接口来监听这个事件,并在 <code>onApplicationEvent</code> 方法中执行一些自定义的逻辑。<br>对于 Spring Cloud 中的微服务来说,当收到 <code>ContextClosedEvent</code> 事件时,可以做以下几件事情:</p><ul><li>从注册中心注销当前服务,这样就不会再有新的请求进入。</li><li>拒绝或者延迟新的请求,这样就可以保证正在处理的请求不会被中断。</li><li>等待一段时间,让旧的请求处理完毕,或者超时。</li><li>关闭服务,释放资源。</li></ul><p>这样就可以实现优雅下线的逻辑,避免因为服务的变更而造成流量的中断或错误。</p><h3>Spring boot优雅下线的demo</h3><p>在旧版本里面,我们需要实现 <code>TomcatConnectorCustomizer</code> 和 <code>ApplicationListener<ContextClosedEvent></code> 接口,然后就可以在 <code>customize</code> 方法中获取到 Tomcat 的 <code>Connector</code> 对象,并在 <code>onApplicationEvent</code> 方法中监听到 Spring 容器的关闭事件。<br>在2.3及以后版本,我们只需要在<code>application.yml</code>中添加几个配置就能启用优雅关停了。</p><pre><code># 开启优雅停止 Web 容器,默认为 IMMEDIATE:立即停止
server:
shutdown: graceful
# 最大等待时间
spring:
lifecycle:
timeout-per-shutdown-phase: 30s</code></pre><p>这个开关的具体实现逻辑在我们在 <code>GracefulShutdown</code> 里。<br>然后我们需要添加actuator依赖,然后在配置中暴露<code>actuator</code>的<code>shutdown</code>接口。</p><pre><code># 暴露 shutdown 接口
management:
endpoint:
shutdown:
enabled: true
endpoints:
web:
exposure:
include: shutdown</code></pre><p>这个时候,我们调用<a href="https://link.segmentfault.com/?enc=gYPb3at4NkDv3eOaCkVZjg%3D%3D.sMvePMN0%2BFYIdsxAl8HbdYb7WjBhbh5MAqR4lpajzJUUo1zIz%2BgcmveoQ9lak5bi" rel="nofollow">http://localhost:8080/actuator/shutdown</a>就可以执行优雅关停了,它会返回如下内容:</p><pre><code>{
"message": "Shutting down, bye..."
}</code></pre><h4>优缺点</h4><p>我觉得这种方法有以下的优点和缺点:<br>优点:</p><ul><li>简单易用,只需要简单的配置,就可以实现优雅下线的逻辑。</li><li>适用于 Tomcat 作为内嵌容器的 Spring Boot 应用,不需要额外的配置或依赖。</li><li>可以保证正在处理的请求不会被中断,而新的请求不会进入,避免了服务的变更造成流量的中断或错误。</li></ul><p>缺点:</p><ul><li>只适用于 Tomcat 作为内嵌容器的 Spring Boot 应用,如果使用其他的容器或部署方式,可能需要另外的实现。</li><li>需要等待一定的时间,让正在处理的请求完成或超时,这可能会影响服务的停止速度和资源的释放。</li><li>如果正在处理的请求过多或过慢,可能会导致线程池无法优雅地关闭,或者超过系统的终止时间,造成强制关闭。</li></ul><h3>Docker优雅下线的demo</h3><p>这里用一个简单的JS应用来演示docker实现无损下线的过程。<br>首先,我们需要创建一个 Dockerfile 文件,用于定义一个简单的应用容器,代码如下:</p><pre><code># 基于 node:14-alpine 镜像
FROM node:14-alpine
# 设置工作目录
WORKDIR /app
# 复制 package.json 和 package-lock.json 文件
COPY package*.json ./
# 安装依赖
RUN npm install
# 复制源代码
COPY . .
# 暴露 3000 端口
EXPOSE 3000
# 启动应用
CMD [ "node", "app.js" ]</code></pre><p>然后,我们需要创建一个 app.js 文件,用于定义一个简单的 web 应用,代码如下:</p><pre><code>// 引入 express 模块
const express = require('express');
// 创建 express 应用
const app = express();
// 定义一个响应 /hello 路径的接口
app.get('/hello', (req, res) => {
// 返回 "Hello, I am app" 字符串
res.send('Hello, I am app');
});
// 监听 3000 端口
app.listen(3000, () => {
// 打印日志信息
console.log('App listening on port 3000');
});</code></pre><p>接下来,我们需要在终端中执行以下命令,来构建和运行我们的应用容器,并查看页面结果。</p><pre><code># 构建镜像,命名为 app:1.0.0
docker build -t app:1.0.0 .
# 运行容器,命名为 app-1,映射端口为 3001:3000
docker run -d --name app-1 -p 3001:3000 app:1.0.0
# 查看容器运行状态和端口映射信息
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a8a9f9f7c6c4 app:1.0.0 "docker-entrypoint.s…" 10 seconds ago Up 9 seconds 0.0.0.0:3001->3000/tcp app-1
# 在浏览器中访问 <http://localhost:3001/hello> ,可以看到返回 "Hello, I am app" 字符串
</code></pre><p>这个时候假设我们要发布一个新版本的应用,我们需要修改 app.js 文件中的代码,把返回的字符串修改为 “<em>Hello, I am app v2</em>”。<br>然后,我们需要在终端中执行以下命令,来构建和运行新版本的应用容器:</p><pre><code># 构建镜像,命名为 app:2.0.0
docker build -t app:2.0.0 .
# 运行容器,命名为 app-2,映射端口为 3002:3000
docker run -d --name app-2 -p 3002:3000 app:2.0.0
# 查看容器运行状态和端口映射信息
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b7b8f8f7c6c4 app:2.0.0 "docker-entrypoint.s…" 10 seconds ago Up 9 seconds 0.0.0.0:3002->3000/tcp app-2
a8a9f9f7c6c4 app:1.0.0 "docker-entrypoint.s…" 2 minutes ago Up 2 minutes 0.0.0.0:3001->3000/tcp app-1
# 在浏览器中访问 <http://localhost:3002/hello> ,可以看到返回 "Hello, I am app v2" 字符串
</code></pre><p>接下来,需要优雅地下线旧版本的应用容器,让它完成正在处理的请求,然后停止接收新的请求,最后退出进程。</p><pre><code># 向旧版本的应用容器发送 SIGTERM 信号,让它优雅地终止
docker stop app-1
# 查看容器运行状态和端口映射信息
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b7b8f8f7c6c4 app:2.0.0 "docker-entrypoint.s…" 2 minutes ago Up 2 minutes 0.0.0.0:3002->3000/tcp app-2
# 在浏览器中访问 <http://localhost:3001/hello> ,可以看到无法连接到服务器的错误</code></pre><p>这样,我们就实现了通过 Docker 来做优雅下线的过程,保证正在处理的请求不会被中断,而新的请求会被路由到新版本的应用上。<br>这里主要用到了<code>docker stop</code>命令。<code>docker stop</code>命令会向容器发送 <code>SIGTERM</code> 信号,这是一种优雅终止进程的方式,它会给目标进程一个清理善后工作的机会,比如完成正在处理的请求,释放资源等。如果目标进程在一定时间内(默认为 10 秒)没有退出,<code>docker stop</code> 命令会再发送 <code>SIGKILL</code> 信号,强制终止进程。<br>所以,使用 <code>docker stop</code> 命令能实现优雅下线的前提是,容器中的应用能够正确地响应 <code>SIGTERM</code> 信号,并在收到该信号后执行清理工作。如果容器中的应用忽略了 <code>SIGTERM</code> 信号,或者在清理工作过程中出现异常,那么 <code>docker stop</code> 命令就无法实现优雅下线的效果。<br>让容器中的应用正确地响应 <code>SIGTERM</code> 信号的方法,主要取决于容器中的 1 号进程是什么,以及它如何处理信号。如果容器中的 1 号进程就是应用本身,那么应用只需要在代码中为 SIGTERM 信号注册一个处理函数,用于执行清理工作和退出进程。例如,在 Node.js 中,可以这样写:</p><pre><code>// 定义一个处理 SIGTERM 信号的函数
function termHandler() {
// 执行清理工作
console.log('Cleaning up...');
// 退出进程
process.exit(0);
}
// 为 SIGTERM 信号注册处理函数
process.on('SIGTERM', termHandler);</code></pre><h2>总结</h2><p><img src="/img/bVc71Hq" alt="image.png" title="image.png"></p><h3>优雅上下线的价值</h3><p>在微服务实践中,实现优雅上下线能给我们带来以下好处:</p><ol><li>最小化服务中断:通过优雅上下线,可以最小化服务中断的时间和影响范围,从而确保服务的可用性和稳定性。</li><li>避免数据丢失:优雅下线可以确保正在处理的请求能够完成,避免数据丢失和请求失败。</li><li>提高用户体验:优雅上下线可以确保用户在使用服务时不会遇到任何中断或错误,从而提高用户体验和满意度。</li><li>简化部署流程:通过使用自动化工具和流程,可以简化部署流程,减少人工干预和错误,提高部署效率和质量。</li><li>提高可维护性:通过使用监控和日志记录工具,可以及时发现和解决问题,提高服务的可维护性和可靠性。</li></ol><p>这些好处可以帮助企业提高服务质量和效率,提升用户满意度和竞争力。</p><h3>优雅上下线的挑战</h3><p>但同时,优雅上下线也面临一些挑战:</p><ol><li>复杂性增加:微服务架构通常由多个服务组成,每个服务都有自己的生命周期和依赖关系,因此优雅上下线需要考虑多个服务之间的交互和协调,增加了系统的复杂性。</li><li>部署流程复杂:优雅上下线需要使用自动化工具和流程,这需要投入大量的时间和资源来构建和维护,增加了部署流程的复杂性。</li><li>数据一致性问题:优雅下线需要确保正在处理的请求能够完成,但这可能会导致数据一致性问题,需要采取措施来解决这个问题。</li><li>人员技能要求高:微服务架构需要具备更高的技术水平和技能,需要拥有更多的开发和运维经验,这对企业的人员要求较高。</li></ol><p>综上所述,企业需要认真考虑这些挑战,并采取相应的措施来解决这些问题,以确保在微服务实践中更好的落地优雅上下线。</p>
如何用Serverless实现视频剪辑批量化、自动化与定制化
https://segmentfault.com/a/1190000041594084
2022-03-23T11:31:34+08:00
2022-03-23T11:31:34+08:00
Woody
https://segmentfault.com/u/woodyyan
0
<h2>前言</h2><p><img src="/img/bVcYGF1" alt="1.jpg" title="1.jpg"></p><p>开始讲之前先解决大家看到这个标题时心里的3个疑惑:</p><ol><li>视频剪辑不是用Adobe的软件就可以做了吗?</li><li>为什么要用Serverless?</li><li>如何写代码做视频剪辑?</li></ol><h4>首先说说哪些视频剪辑场景是Adobe等软件无法完成的</h4><p>大家平常接触到的视频剪辑通常都是使用Premiere,AE等这类专业工具来完成视频剪辑。他们能完成一些复杂的效果,比如做宣传视频,广告视频等。</p><p>但有些企业在某些业务场景下是期望能<strong>批量且自动化</strong>的完成视频剪辑。</p><p>比如以下几种场景:</p><ol><li>假设学校期望能在学生上完网课之后马上呈现所有学生学习过程中的精彩视频,配上学校的logo和宣传语等,让学生一键分享自己的成果。假设有1万个学生,需要为每个学生制作独一无二的视频,所以需要批量且自动化的完成1万个不同的视频剪辑。</li><li>某次营销活动中,需要为不同的用户生成不同的头像视频来吸引用户参与。每个用户的头像都是独一无二的,生成的视频也是独一无二的,用户可能成千上万,因此自动化完成是必须的条件。</li><li>网红运营公司期望能给所有主播生成统一的营业视频。可能有100个主播,专门找一个人剪辑100个视频好像勉强能接受,但如果每周都要剪一次不同的视频呢?所以自动化,批量和可定制化的剪辑就成了主要需求。</li></ol><p>以上的场景中有三个特点:</p><ol><li>批量</li><li>自动化</li><li>可定制</li></ol><p>对于符合以上特点的场景,是传统的视频剪辑工具或者模版化的视频处理软件无法轻松完成的。</p><h4>再来说说为什么用Serverless</h4><p>因为视频剪辑这样的业务有几个特点:</p><ul><li>使用时段集中。</li><li>计算量大。</li></ul><p>单独购买高规格的服务器利用率很低,买便宜的服务器计算能力又跟不上。</p><p>因此Serverless按量计费的特点,以及高性能的计算能力,完美匹配了这样的需求场景。</p><p>既能达到100%的利用率,又能按量使用它的高性能计算能力。</p><p>同时,Serverless拥有多变的可编程环境,可以使用熟悉的编程语言,灵活性很高。</p><h4>最后说说如何写代码做视频剪辑</h4><p>本文章提到的所有视频剪辑的功能,都是用FFmpeg这个工具,所以先给大家讲讲什么是FFmpeg。</p><p><a href="https://link.segmentfault.com/?enc=oWcwn6ipmigZ%2FpWMk3tUiA%3D%3D.vV9PdZHySf0oMiJ3eBLVF4hyiSbn9RH6ZV79bxWK3Lc%3D" rel="nofollow">FFmpeg</a>是一个用来做视频处理的开源工具,它有非常强大的功能,它支持视频剪辑、视频转码、视频编辑、音频处理、添加文字、视频拼接、拉流推流直播等功能。</p><p>我们通过不同的FFmpeg命令就可以编程完成不同的视频剪辑功能,组合编排起来,就可以应对各种批量自动化的场景了。</p><h2><strong><em><em>视频剪辑批量化、自动化与定制化实践</em></strong></em></h2><p>常见的视频剪辑场景主要包含以下几种:</p><ol><li>视频转码</li><li>视频裁剪</li><li>视频加文字</li><li>视频加图片</li><li>视频拼接</li><li>视频加音频</li><li>视频转场</li><li>视频特效</li><li>视频加速慢速播放</li></ol><p>接下来给大家展示一些具体的FFmpeg命令例子,如果你在本地安装了FFmpeg,也可以在本地执行这些命令。关于怎么安装FFmpeg,可以去看<a href="https://link.segmentfault.com/?enc=lCddJFjDqc6Ltx8Km%2BaKhQ%3D%3D.ENHBSWSl1mB1u1Es7AcnRmxth%2FzWphPRVoBvTdt%2BGks%3D" rel="nofollow">官网</a>的教程。</p><pre><code class="jsx">// 将MOV视频转成mp4视频
ffmpeg -i input.mov output.mp4
// 将原视频的帧率修改为24
ffmpeg -i input.mp4 -r 24 -an output.mp4
// 将mp4视频转为可用于直播的视频流
ffmpeg -i input.mp4 -codec: copy -bsf:v h264_mp4toannexb -start_number 0 -hls_time 10 -hls_list_size 0 -f hls output.m3u8
// 将视频分别变为480x360,并把码率改400
ffmpeg -i input.mp4 -vf scale=480:360,pad=480:360:240:240:black -c:v libx264 -x264-params nal-hrd=cbr:force-cfr=1 -b:v 400000 -bufsize 400000 -minrate 400000 -maxrate 400000 output.mp4
// 给视频添加文字,比如字幕、标题等。
// `fontfile`是要使用的字体的路径,`text`是你要添加的文字,
// `fontcolor`是文字的颜色,`fontsize`是文字大小,`box`是给文字添加底框。
// `box=1`表示enable,`0`表示disable,`boxcolor`是底框的颜色,black@0.5表示黑色透明度是50%,`boxborderw`是底框距文字的宽度
// `x`和`y`是文字的位置,`x`和`y`不只支持数字,还支持各种表达式,具体可以去官网查看
ffmpeg -i input.mp4 -vf "drawtext=fontfile=/path/to/font.ttf:text='你的文字':fontcolor=white:fontsize=24:box=1:boxcolor=black@0.5:boxborderw=5:x=(w-text_w)/2:y=(h-text_h)/2" -codec:a copy output.mp4
// 给视频添加图片,比如添加logo、头像、表情等。filter_complex表示复合的滤镜,overlay表示表示图片的x和y,enable表示图片出现的时间段,从0-20秒
ffmpeg -i input.mp4 -i avatar.JPG -filter_complex "[0:v][1:v] overlay=25:25:enable='between(t,0,20)'" -pix_fmt yuv420p -c:a copy output.mp4
// 视频拼接,list.txt里面按顺序放所有要拼接的视频的文件路径,如下。
// 注意,如果视频的分辨率不一致会导致拼接失败。
ffmpeg -f concat -safe 0 -i list.txt -c copy -movflags +faststart output.mp4
// list.txt的格式如下
file 'xx.mp4'
file 'yy.mp4'
// 视频加音频,stream_loop表示是否循环音频内容,-1表示无限循环,0表示不循环。shortest表示最短的MP3输入流结束时完成编码。
ffmpeg -y -i input.mp4 -stream_loop -1 -i audio.mp3 -map 0:v -map 1:a -c:v copy -shortest output.mp4</code></pre><p>FFmpeg能做的事情非常多,这里就不一一讲解了。更多的玩法可以在FFmpeg<a href="https://link.segmentfault.com/?enc=kKRWv46MIW0zBOSTE%2BmOqQ%3D%3D.wBpe9lJPjNa8tIMzSqVcSAUDzWp%2BEJeCUfHt6hiRBfE%3D" rel="nofollow">官网</a>上探索探索。</p><p>对于音频的编辑也是同样的道理,FFmpeg也支持单独对音频进行编辑。</p><h4>如何运行FFmpeg命令</h4><p>因为Python运行这些命令比较便捷,所以我们可以使用python来运行所有的FFmpeg命令。同时python在serverless云函数上运行性能也比较好,部署也方便。</p><p>通过Python来使用FFmpeg的视频剪辑代码在文章最后有开源链接。并且在官网上也有模版可以直接使用,覆盖了常见的音视频剪辑等操作。</p><p>这里就展示一个简单的调用代码示例。</p><pre><code class="python">child = subprocess.run('./ffmpeg -i input.mov output.mp4',
stdout=subprocess.PIPE,
stderr=subprocess.PIPE, close_fds=True, shell=True)
if child.returncode == 0:
print("success:", child)
else:
print("error:", child)
raise KeyError("处理视频失败, 错误: ", child)</code></pre><h2>在serverless部署</h2><p>上面提到的常见的视频剪辑场景我已经实现并开源了,下载代码直接部署到serverless就可以使用了。</p><p><a href="https://link.segmentfault.com/?enc=FR5zc83dAqOE3imhFSXd6Q%3D%3D.urwpr8%2F7v259wNAfXnQgMkDRBGVAonLdzJSA84FEtpXIgV5d0rWyw1rGogOK4IFZ" rel="nofollow">https://github.com/woodyyan/ffmpeg-composition</a></p><p><a href="https://link.segmentfault.com/?enc=UisDY1S59GxkTj7H9UikRA%3D%3D.kg1i2IXJ5hMvTABV%2Fazk%2FhDiDty1cXo%2F4hjoveQJRlxeOXwHbVpjHmNKfmuDCe9Y" rel="nofollow">https://github.com/woodyyan/ffmpeg-splice</a></p><p>这里分为了两个函数,一个负责处理单个视频,一个负责把多个视频拼接成一个视频并配上背景音乐。</p><p>目前支持以下功能:</p><ol><li>在视频中添加文字</li><li>视频分辨率转换</li><li>在视频中添加图片</li><li>视频拼接</li><li>添加背景音乐</li></ol><p>源码里展示的只是常见的一些视频剪辑场景,大家可以根据自己的业务需要,编写自己的视频剪辑逻辑。</p><h4>Serverless部署</h4><p><strong>方式一:Github Action自动部署</strong></p><ol><li>Fork仓库。</li><li>在仓库的Settings-Secrets-Actions中添加<code>TENCENT_SECRET_ID</code>和<code>TENCENT_SECRET_KEY</code>两个密钥。ID和KEY可以在腾讯云的访问控制里面获取。</li><li>添加之后,在Action中就可以发起部署了。每次修改代码推送后,也会自动触发Action部署。</li><li>如果需要有一些自定义的配置,请修改serverless.yml。</li><li>云函数最终会自动部署到<code>TENCENT_SECRET_ID</code>所在的账号下。</li></ol><p><strong>方式二:云函数控制台手动部署</strong></p><ol><li>下载代码。</li><li>在根目录把所有文件和文件夹一起打包成一个ZIP文件。</li><li>去<a href="https://link.segmentfault.com/?enc=NtQPnv5T0dmrx5R8VVG9Yg%3D%3D.yJycxi4f%2FCZ7Mi7EuC0J1Yq6hpQUagb0M1Io2VnBKbjjAlyeQROk0abK8nfkYcKAZ3Jl3xoquMqPedOU628gKA%3D%3D" rel="nofollow">云函数控制台</a>,新建一个函数。</li><li><p>选择从头开始:</p><ol><li>选择python语言。</li><li>上传ZIP文件。</li><li>函数内存建议选择较大的内存。</li><li>开启异步执行。</li><li>执行超时时间根据视频大小建议设置长一点,比如30秒以上。</li><li>配置触发器,选择API网关触发器,关闭集成响应。</li></ol></li><li>完成部署后,就可以通过API网关的URL开始调用了。</li></ol><h2>真实案例回顾</h2><p><img src="/img/bVcYGF7" alt="2.png" title="2.png"></p><p>一个做网课的学校,需要每次在学生上完网课之后把上网课的录像制作成一段30秒的视频,作为学生的学习成果。</p><p>此案例有几个关键的信息点:</p><ol><li>通常一堂课有200个学生,需要同时制作200个视频。</li><li>需要把1小时的上课视频剪辑成30秒。</li><li>由于每个学生的上课屏幕有所不同,因此录制的视频都是不同的。</li><li>最终的成果视频还需要加上学生的名字和头像。</li><li>学生结束上课的时间很集中,因此制作视频时会有短时高并发。</li><li>每次上完课的时候才会需要制作视频,时段比较固定且集中。</li></ol><p>综合上述特点,用Serverless来做这样的视频剪辑带来了多个好处:</p><ol><li>解决了200个并发的问题,不需要自己搭建过多的服务器。</li><li>解决了只在发生时段使用的问题,其他时段都没有成本产生。</li><li>解决了需要较强计算能力快速制作视频的问题。</li></ol><p>下面是这个案例的参考架构图。</p><p><img src="/img/bVcYGF8" alt="3.png" title="3.png"></p><h2>总结</h2><p>通过编排、组合、复用上面列举的各种音视频剪辑的场景,就能制作出各种各样想要的效果。</p><p>然后把视频剪辑中用来控制各种效果的参数,变成调用服务时传入的参数,就能实现各种效果的定制化了。</p><p>最后再总结一下通过这种写代码的方式完成视频剪辑的使用场景:</p><ol><li>解决通过修改个别参数来批量制作视频的场景。</li><li>解决通过用户触发来自动化制作视频的场景。</li><li>解决不同场景需要不同定制化的制作视频的场景。</li></ol><p>同时,利用serverless来完成视频剪辑,同样也解决了以下几个问题:</p><ol><li>因为通常视频剪辑不是全天运行,利用serverless按量付费的特性能优化成本。</li><li>因为视频剪辑通常是重计算场景,利用serverless可选的高规格配置来应对这种重计算。</li><li>在批量制作视频的场景中通常会存在高并发,利用serverless自动弹性伸缩的特性能轻松应对高并发。</li></ol><p>关于Serverless使用上或者视频剪辑大家有什么问题,欢迎给我留言。</p>
如何做Serverless自动化部署
https://segmentfault.com/a/1190000041473029
2022-02-28T17:48:15+08:00
2022-02-28T17:48:15+08:00
Woody
https://segmentfault.com/u/woodyyan
0
<h2>前言</h2><p><img src="/img/bVcYaVs" alt="1.png" title="1.png"></p><p>随着敏捷和DevOps的流行,CI/CD已经成了所有开发者在开发过程中必不可少的最佳实践,主要目标是以更快的速度、更短的周期向用户交付行之有效的软件。</p><p>它能给我们带来如下好处:</p><ul><li>缩短发布周期</li><li>降低风险</li><li>提高代码质量</li><li>更高效的反馈循环</li><li>可视化过程</li></ul><p>因此在Serverless越来越流行的今天,如何让Serverless的项目也能快速的搭建CI/CD,这是这篇文章的重点。</p><p>习惯了CI/CD的用户可能都期望有一个快速搭建自动化部署的教程,所以这篇文章会以下面几个流行的平台来讲解如何搭建自动化部署,让你能够推送代码就自动完成部署。</p><ul><li>Github</li><li>Jenkins</li><li>Coding</li></ul><h2><strong><em><em>基于 GitHub 的自动化部署</em></strong></em></h2><p><a href="https://link.segmentfault.com/?enc=lP6M9%2BQF2tXYMutEjdPnCg%3D%3D.6dfFpjb7g8A6ycZCJ1s32hhjJLVfDjOW%2BxVhg8kKiOhnDpwokZwGijnKB9HnqtLE" rel="nofollow">GitHub Actions</a>是Github推出的自动化软件开发工作流。 通过Actions可以执行任何任务,其中就包括 CI/CD。</p><p><img src="/img/bVcWLYv" alt="2.png" title="2.png"></p><h4><strong><em><em>前提条件</em></strong></em></h4><ul><li>已托管你的 Serverless 项目代码到Github。</li><li>项目中必须包含Serverless framework部署需要用到的<code>serverless.yml</code>。<code>serverless.yml</code>的使用方式请参考<a href="https://link.segmentfault.com/?enc=G80Zx6xB8GyvF8Z4aqS3UQ%3D%3D.GLH4edpGugAz7J1LAwPfSsK350DWKtIdUMDE7PExK%2B8o1RR8mc5WvwTuJfTsgTVizZTXNUTbAqRuhAkw50YM7g%3D%3D" rel="nofollow">官网</a>。</li><li>如果是Web函数,需保证根目录有<code>scf_bootstrap</code>文件,具体请参考<a href="https://link.segmentfault.com/?enc=MJwGiRDPVZG7HLIXE%2FoZuw%3D%3D.7yXXNvms8L4wY1cYYh0OoMMhadvLCkE9SzRI7DMTgbI5%2B7Pp6974hrFYLHQA79k2fwGzXD7QAoCOwp9hnDMsSg%3D%3D" rel="nofollow">官网</a>。</li></ul><h4><strong><em><em>操作步骤</em></strong></em></h4><p>为了让这个部署过程更简单,我在GitHub的市场中发布一个腾讯云Serverless部署的Action来帮助大家快速完成自动化部署。</p><p>在GitHub的Marketplace中搜索<a href="https://link.segmentfault.com/?enc=K9cSeQNE5GMHnB2j26OQjg%3D%3D.0s0jB6n5J2JYJAna07xEaJymO8kTjYu4DMye7djIrqQKU9D%2F%2BbebtCjXgwgA9NJUjjmTrD2G1U5P7ggEWoh2sU6l5oszbQbkKU2VvY9pRfo%3D" rel="nofollow">tencent serverless</a>就可以找到。如下图。里面有详细的Action代码。</p><p><img src="/img/bVcYaVy" alt="3.png" title="3.png"></p><p>首先,在Actions里面选择Set up a workflow yourself,如下图。</p><p><img src="/img/bVcYaVz" alt="4.png" title="4.png"></p><p>如果知道如何使用Action,那么直接用下面这句就可以了,里面封装了安装Serverless framework和执行部署命令的步骤。</p><pre><code> - name: serverless scf deploy
uses: woodyyan/tencent-serverless-action@main</code></pre><p>如果不知道如何使用Action,可以根据不同的语言选择下列不同的yml写法,下面我列举了Python、Java、NodeJS的写法。</p><p><strong>适用于<code>Python</code>项目</strong></p><pre><code># 当代码推动到 main 分支时,执行当前工作流程
# 更多配置信息: https://docs.github.com/cn/actions/getting-started-with-github-actions
name: deploy serverless scf
on: #监听的事件和分支配置
push:
branches:
- main
jobs:
deploy:
name: deploy serverless scf
runs-on: ubuntu-latest
steps:
- name: clone local repository
uses: actions/checkout@v2
- name: deploy serverless
uses: woodyyan/tencent-serverless-action@main
env: # 环境变量
STAGE: dev #您的部署环境
SERVERLESS_PLATFORM_VENDOR: tencent #serverless 境外默认为 aws,配置为腾讯
TENCENT_SECRET_ID: ${{ secrets.TENCENT_SECRET_ID }} #您的腾讯云账号 sercret ID,请在Settings-Secrets中配置
TENCENT_SECRET_KEY: ${{ secrets.TENCENT_SECRET_KEY }} #您的腾讯云账号 sercret key,请在Settings-Secrets中配置
</code></pre><p><strong>适用于<code>Java</code>项目,请仔细看代码中的备注说明</strong></p><pre><code>name: deploy serverless scf
on: #监听的事件和分支配置
push:
branches:
- main
jobs:
build-and-deploy:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v2
- name: Set up JDK 11
uses: actions/setup-java@v2
with:
java-version: '11'
distribution: 'temurin'
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
settings-path: ${{ github.workspace }} # location for the settings.xml file
- name: Build with Gradle # Gradle项目用这个
uses: gradle/gradle-build-action@937999e9cc2425eddc7fd62d1053baf041147db7
with:
arguments: build
- name: Build with Maven # Maven项目用这个
run: mvn -B package --file pom.xml
- name: create zip folder # 此步骤仅用于Java Web函数,用于存放jar和scf_bootstrap文件。Java事件函数只需要在Serverless.yml中指定Jar目录就好。
run: mkdir zip
- name: move jar and scf_bootstrap to zip folder # 此步骤仅用于Java Web函数,用于移动jar和scf_bootstrap文件。Java事件函数只需要在Serverless.yml中指定Jar目录就好。注意如果是Maven编译请修改下面的jar路径为/target。
run: cp ./build/libs/XXX.jar ./scf_bootstrap ./zip
- name: deploy serverless
uses: woodyyan/tencent-serverless-action@main
env: # 环境变量
STAGE: dev #您的部署环境
SERVERLESS_PLATFORM_VENDOR: tencent #serverless 境外默认为 aws,配置为腾讯
TENCENT_SECRET_ID: ${{ secrets.TENCENT_SECRET_ID }} #您的腾讯云账号 sercret ID
TENCENT_SECRET_KEY: ${{ secrets.TENCENT_SECRET_KEY }} #您的腾讯云账号 sercret key</code></pre><p><strong>适用于<code>NodeJS</code>项目</strong></p><pre><code># 当代码推动到 main 分支时,执行当前工作流程
# 更多配置信息: https://docs.github.com/cn/actions/getting-started-with-github-actions
name: deploy serverless scf
on: #监听的事件和分支配置
push:
branches:
- main
jobs:
deploy:
name: deploy serverless scf
runs-on: ubuntu-latest
steps:
- name: clone local repository
uses: actions/checkout@v2
- name: install dependency
run: npm install
- name: build
run: npm build
- name: deploy serverless
uses: woodyyan/tencent-serverless-action@main
env: # 环境变量
STAGE: dev #您的部署环境
SERVERLESS_PLATFORM_VENDOR: tencent #serverless 境外默认为 aws,配置为腾讯
TENCENT_SECRET_ID: ${{ secrets.TENCENT_SECRET_ID }} #您的腾讯云账号 sercret ID,请在Settings-Secrets中配置
TENCENT_SECRET_KEY: ${{ secrets.TENCENT_SECRET_KEY }} #您的腾讯云账号 sercret key,请在Settings-Secrets中配置</code></pre><p>最后,由于部署的时候需要用到腾讯云的<code>TENCENT_SECRET_ID</code>和<code>TENCENT_SECRET_KEY</code>,所以需要在Github代码仓库的设置中的Secrets里面配置这两个变量。如下图所示。ID和KEY可以在腾讯云的访问控制里面获取。</p><p><img src="/img/bVcYaVJ" alt="5.png" title="5.png"></p><p>配置完成之后,每次推送代码,都将会自动触发部署流程,同时在Actions中可以实时看到执行结果与错误日志。如下图。</p><p>大家还可以根据项目需要,在流程中添加测试、安全检查、发布等步骤。</p><p><img src="/img/bVcYaVR" alt="6.png" title="6.png"></p><h2>基于Jenkinsfile的自动化部署</h2><p>Jenkinsfile是通用于Jenkins、Coding等平台的,因此只需要配置好Jenkinsfile,则能在这些平台上完成自动化部署。</p><h4><strong><em><em>前提条件</em></strong></em></h4><ul><li>已托管你的 Serverless 项目到 Coding/Github/Gitlab/码云等平台。</li><li>项目中必须包含Serverless framework部署需要用到的<code>serverless.yml</code>。<code>serverless.yml</code>的使用方式请参考<a href="https://link.segmentfault.com/?enc=%2FkPd8FjpJSD47JOVHPq3hQ%3D%3D.oY594ywBdmy0bWxZYIjlMVvCzKwx4crh5gBnNwcDdN%2BgwJ%2FVBEk%2FS8pN%2FNpwrsG4rWOR30yHNYQNbkzNPv2X6g%3D%3D" rel="nofollow">官网</a>。</li><li>如果是Web函数,需保证根目录有<code>scf_bootstrap</code>文件,具体请参考<a href="https://link.segmentfault.com/?enc=iUL%2FxRW0Wv9nVckuLF2paw%3D%3D.TlRizJaptnxXtMn81uVcoHHLtkrvBXmhMpQTyWZRHWbtS9doN6VYay9JsH%2BdrpnHddn5tjYAfWeo2sZKmFgBiA%3D%3D" rel="nofollow">官网</a>。</li></ul><h4><strong><em><em>操作步骤</em></strong></em></h4><p>根据不同语言的需要,我把所有语言需要用到的语法都写在下面的Jenkinsfile中了,适用于Python、Java、NodeJS,请仔细阅读注释。</p><pre><code class="python">pipeline {
agent any
stages {
stage('检出') {
steps {
checkout([$class: 'GitSCM', branches: [[name: env.GIT_BUILD_REF]],
userRemoteConfigs: [[url: env.GIT_REPO_URL, credentialsId: env.CREDENTIALS_ID]]])
}
}
stage('Package'){ // 此stage仅用于Java项目
steps{
container("maven") {
echo 'Package start'
sh "mvn package" // 此行用于Java Maven项目
sh "./gradlew build" // 此行用于Java Gradle项目
sh "mkdir zip" // 此行仅用于Java Web函数,用于存放jar和scf_bootstrap文件。Java事件函数只需要在Serverless.yml中指定Jar目录就好。
sh "cp ./build/libs/XXX.jar ./scf_bootstrap ./zip" // 此行仅用于Java Web函数,用于移动jar和scf_bootstrap文件。Java事件函数只需要在Serverless.yml中指定Jar目录就好。注意如果是Maven编译请修改下面的jar路径为/target。
}
}
}
stage('安装依赖') {
steps {
echo '安装依赖中...'
sh 'npm i -g serverless'
sh 'npm install' // 此行用于NodeJS项目
echo '安装依赖完成.'
}
}
stage('部署') {
steps {
echo '部署中...'
withCredentials([
cloudApi(
credentialsId: "${env.TENCENT_CLOUD_API_CRED}",
secretIdVariable: 'TENCENT_SECRET_ID',
secretKeyVariable: 'TENCENT_SECRET_KEY'
),
]) {
// 生成凭据文件
sh 'echo "TENCENT_SECRET_ID=${TENCENT_SECRET_ID}\nTENCENT_SECRET_KEY=${TENCENT_SECRET_KEY}" > .env'
// 部署
sh 'sls deploy --debug'
// 移除凭据
sh 'rm .env'
}
echo '部署完成'
}
}
}
}</code></pre><p>使用上面的Jenkinsfile就可以在Jenkins、coding等平台一键完成CI/CD配置了。</p><p>注意,需要在平台中配置腾讯云需要用到的<code>TENCENT_SECRET_ID</code>和<code>TENCENT_SECRET_KEY</code>这两个变量。</p><h2>总结</h2><p>作为开发者,总是希望所有代码工作都是自动化完成,都能提高效率。因此,熟练的掌握如何快速配置自动化的CI/CD流程,是每个开发者必须掌握的技能之一。</p><p>在这里分享这些开箱即用的配置,也是希望能大大减少大家的学习成本,快速上手开始核心业务开发。</p><p>未来我还会继续探索更多的适用于Serverless的DevOps实践,与大家分享。</p><p>如果有任何疑问或在操作中遇到任何困难可以在文章下方留言,我会回复大家。</p>
如何用Serverless云函数做免费私域运营机器人
https://segmentfault.com/a/1190000041390452
2022-02-11T14:48:33+08:00
2022-02-11T14:48:33+08:00
Woody
https://segmentfault.com/u/woodyyan
0
<h2>关于私域流量</h2><p><img src="/img/bVcXPG1" alt="1.png" title="1.png"></p><p>近几年,私域流量运营的话题被提及得越来越多。</p><p>私域流量是指从公域(internet)、它域(平台、媒体渠道、合作伙伴等)引流到自己私域(官网、客户名单),以及私域本身产生的流量(访客)。 私域流量是可以进行二次以上链接、触达、发售等市场营销活动客户数据。 </p><p>而私域流量运营很重要的一点就是如何能自动化智能化的进行客户运营。</p><p>目前各大公司的办公软件都支持机器人这种应用形式,而这种机器人则是我们做私域流量运营的重要一环。</p><h2>机器人能做什么</h2><p>机器人在私域流量运营中可以做包括但不限于以下事情:</p><ol><li>消息推送</li><li>智能客服</li><li>客户管理</li><li>建群引流</li><li>活动营销</li><li>企业互联</li></ol><p>这些场景名词可能有些抽象,可以举几个具体例子。</p><ul><li>比如,用户进群之后会收到机器人自动发送的欢迎仪式,里面附带新用户代金券等,同时此消息是仅他可见,不会打扰其他用户。</li><li>比如,用户通过询问智能客服机器人就能得到很多常见的答案,省去了人工成本。</li><li>比如,机器人自动在群里发起某营销活动的报名,无需人工收集。</li><li>再比如,通过客户管理,可以给客户打标签,针对不同的客户,自动发送不同的活动优惠。</li><li>再再比如,通过机器人收集广告投放获取的商机,自动创建商机线索,并同步到群里自动@相关销售,闭环整个商机发现路径。</li></ul><p>可以想象的空间有很多很多。</p><h2>为什么是Serverless呢</h2><p>为什么选择serverless来做呢,好处主要有以下几点:</p><ul><li>机器人的通信都是通过HTTP请求与企业微信通信,而serverless按调用次数收费,拥有极高的性价比。</li><li>机器人通常在晚上都没有人使用,如果使用传统的服务器部署会有较高的闲置率,用serverless可以把利用率做到近乎百分百。</li><li>机器人可能会涉及多个使用场景,可以针对不同的场景使用不同的FaaS云函数,做到细粒度的管理和问题隔离。</li><li>腾讯云云函数支持所有主流语言,无需关心服务器,开发快,周期短,一个机器人从开发到上线最快只需要1小时。</li></ul><p><strong>为什么说免费呢?</strong></p><p>因为腾讯云云函数包含有免费额度。而机器人的使用并不是高频调用,所以免费额度足以涵盖所有的使用量。</p><p>免费的羊毛薅起来吧!<br><img src="/img/bVcXPG3" alt="1.2.png" title="1.2.png"></p><p>这篇文章将选择企业微信作为平台,从最基础的场景,讲解如何用serverless云函数来完成一个企业微信机器人。</p><h2>企业微信机器人原理</h2><p><img src="/img/bVcXPG5" alt="2.png" title="2.png"></p><p>我们先来了解一下企业微信机器人的原理。如上图所示,左边表示我们的serverless云函数机器人,右边是企业微信。</p><p>中间的箭头表示两种机器人和企业微信的通信方式:</p><ol><li>机器人单向给企业微信发送消息</li><li>机器人和企业微信双向互发消息</li></ol><p>从图中可以看出,单向通信是蓝色的箭头,因为单向通信没有任何限制,机器人无法获取企业微信的相关信息。这种模式主要适合于所有的通知类的场景。比如消息推送,全局群发等。</p><p>而红色的箭头就有诸多限制了,因为企业微信可以向外发送信息的话,这里就涉及到很多安全问题了。因此企业微信对于这种情况主要做了多方面的限制:</p><ol><li>发送的消息必须经过严格的加解密。</li><li>某些特殊消息内容拥有一定的实效性,比如获取会话信息必须通过一个临时的URL,有效期只有5分钟,且调用一次后失效。</li><li>双向通信的回调URL可以由企业设置一些限制,比如只支持企业内网URL。</li></ol><p>那配合双向通信,就可以做到上面说的所有场景,比如智能客服、客户管理等。</p><h2>机器人实战</h2><p>那我们就从两个简单的场景来讲解一下如何实现一个企业微信机器人。</p><ol><li>消息通知 - 单向通信</li><li>知识库搜索 - 双向通信</li></ol><h4>消息通知</h4><p>首先需要创建一个机器人,创建方式是在任何一个企业微信群里,点击右上角,添加群机器人。<br><img src="/img/bVcXPG6" alt="3.png" title="3.png"></p><p>然后选择新创建一个机器人。<br><img src="/img/bVcXPHb" alt="4.png" title="4.png"><br>创建完成之后,你就获得了一个webhook地址。如下图。<br><img src="/img/bVcXPHc" alt="5.png" title="5.png"></p><p>这个webhook地址就是你推送消息到企业微信的地址。</p><p>推送的消息格式有很多种,支持往群聊会话中发送文本、markdown、图片、图文、文件、模版卡片六种消息类型。</p><p>以文本消息为例,你只需要推送以下JSON内容到webhook地址,企业微信就会收到通知。</p><pre><code class="jsx">{
"chatid":"CHATID1 | CHATID2",
"msgtype":"text",
"text":{
"content":"广州今日天气:29度,大部分多云,降雨概率:60%",
"mentioned_list":["lisi", "@all"],
"mentioned_mobile_list":["13800001111", "@all"]
}
}</code></pre><p>那么以云函数为例,如何创建云函数可以参考<a href="https://link.segmentfault.com/?enc=bQndDrguNwENwyODUZU5MQ%3D%3D.0LNJW2FqkguGPlwZ1Kd0apzJM4YL2MSCG4o8jBIoN%2FMo4hcAoXIr8Dh4YhmLMIGsnKeZzFjdpnOFhKVh%2FFW7Ag%3D%3D" rel="nofollow">官网文档</a>。</p><p>创建好之后,只需要几行代码就能完成一个通知发送机器人。如下图。</p><p>注意要将url替换成你的机器人webhook地址,content必须是utf8编码。<br><img src="/img/bVcXPHf" alt="6.png" title="6.png"></p><p>如果你期望每天早上8点定时推送天气预报,你只需要修改一下上面的代码,从某个天气预报API拿到天气预报,然后设置一个定时触发器,触发周期用CRON表达式定义每天8点触发,如下图。<br><img src="/img/bVcXPHk" alt="7.png" title="7.png"></p><p>这样之后,每天8点你的企业微信群就能收到如下图的消息了。<br><img src="/img/bVcXPHl" alt="8.png" title="8.png"></p><h4>知识库搜索</h4><p>上一个例子是单向通信的例子。那这个例子则是双向通信的例子。</p><p>在企业中,以及在私域流量运营中,我们经常有搜索知识库寻找答案的场景。这里我们就以搜索腾讯云文档为例,来向大家讲解如何完成一个双向通信的知识库搜索机器人。</p><p>我们要做的就是当输入关键字,就去腾讯云文档搜索结果并返回,同时高亮显示关键字和文档链接。</p><p>首先,还是一样的,你需要创建一个云函数。但这个云函数是需要接收企业微信发过来的消息,因此在上一个云函数的基础上,我们需要添加一个API网关触发器,让云函数能接收API请求。</p><p>创建触发器选择API网关触发器,创建好之后如下图,复制访问路径那个URL,它就是企业微信在回调消息的需要填的URL。<br><img src="/img/bVcXPHo" alt="9.png" title="9.png"></p><p>接着到企业微信,鼠标放到你创建的机器人上,点击配置,选择【接收消息配置】,在URL那里填入上面复制的URL。如下图。</p><p>Token和EncodingAESKey可以自己写,也可以随机获取,它是你用来做加密解密时用的。<br><img src="/img/bVcXPHw" alt="10.png" title="10.png"></p><blockquote>💡 当点击“保存”提交以上信息时,企业微信会发送一条验证消息到填写的URL,发送方法为<strong><code>GET</code>。</strong>群机器人的接收消息服务器接收到验证请求后,需要作出正确的响应才能通过URL验证。</blockquote><p>完成了上述设置之后,你在群聊中@机器人并输入你想搜索的关键字,你的云函数就会收到对应的JSON消息,<code>msgContent</code>就是你搜索的关键字。</p><pre><code class="jsx">{
"msgType": "text",
"msgContent": "函数计费",
"chatId": "XXX",
"botKey": "XXX",
"hookUrl": "http://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=XXXX",
"botName": "腾讯云文档搜索助手",
"userName": "XXX·",
"msgId": "CAIQ4",
"chatType": "group",
"chatInfoUrl": "http://qyapi.weixin.qq.com/cgi-bin/webhook/get_chat_info?code=XXX"
}</code></pre><p>这个时候你只需要拿到<code>msgContent</code>的内容,然后去调用腾讯云的文档搜索API,拿到JSON的结果,把JSON结果处理为如下图中的markdown格式,并返回。</p><p>于是我们的腾讯文档搜索助手就做好了,使用效果如下图。<br><img src="/img/bVcXPHB" alt="11.png" title="11.png"></p><p>至此,我们两个企业微信机器人都做好了。</p><p><em>这里就不展示代码了,想看具体怎么写的同学可以去看我的<a href="https://link.segmentfault.com/?enc=7Rt7NluIrEHeiXD6xCS4wQ%3D%3D.aN7T7wtj2%2FCwTOHcaSNwJcXs%2F1xpOLA4Kt2QmgSpv49YDa7t0M7IZiTE5IuMqhYw" rel="nofollow">源码</a>。</em></p><h2>总结</h2><p>我从两个简单的例子去讲解了如何做企业微信机器人,而企业微信机器人是我们做私域流量运营的重要一步,同时Serverless则完美帮我们解决了实现机器人的技术选型。</p><ul><li>随着我们对客户体验和服务体验的追求,我们利用自动化的手段帮我们提高了响应速度,利用智能化帮我们提高了服务准确度。</li><li>在追求售前和售后效率的今天,机器人的使用可以节省人力成本和时间,缩短客户等待时间。</li><li>Serverless作为一种弹性伸缩与按量计费的服务,完美匹配了机器人的使用场景,从成本与效率上帮助企业在私域流量运营场景中业务的快速搭建与迭代。</li><li>Serverless作为一种FaaS服务,通过多个云函数的编排,独立或混合的处理不同的业务场景,做到细粒度的管理,与业务容错隔离。</li></ul><p>未来,我会继续探索Serverless做私域流量运营的更多场景和实践,也会继续和大家分享。</p><p>如果大家有私域流量运营相关的问题,欢迎来和我一起探讨。</p>
如何用Serverless搭建Mock server
https://segmentfault.com/a/1190000041300709
2022-01-18T11:17:34+08:00
2022-01-18T11:17:34+08:00
Woody
https://segmentfault.com/u/woodyyan
0
<h2>前言</h2><h4>什么是Serverless</h4><p>无服务器Serverless是一种云原生开发模型,可使开发人员专注构建和运行应用,而无需管理服务器。</p><p>云函数(Serverless Cloud Function,SCF)则是腾讯云提供的无服务器执行环境,可以在无需购买和管理服务器的情况下运行代码。</p><h4>什么是Mock Server</h4><p>现在的业务系统很少有孤立存在的,它们或多或少需要使用或依赖其他服务,这给我们的联调和测试造成了麻烦。</p><p>为了应对这种情况,我们常会搭建一个临时的server,模拟那些服务,提供模拟数据进行联调和测试。</p><p>这个临时的server就是 mock server 。</p><p>因此mock server通常具有以下特点:</p><ol><li>快速搭建、无需写代码</li><li>能模拟任何数据</li><li>低成本</li><li>简单配置</li></ol><p>也正是这些特点,均符合serverless的特点,因此我们使用serverless来做这件事情再合适不过了。</p><p>接下来我们就用腾讯云的云函数为例,来讲解一下如何快速搭建Mock Server。</p><h2>如何用云函数快速搭建Mock Server</h2><p>目前市面上有很多Mock server工具,开源的不开源的都有。</p><p>这里就用Moco作为例子来教大家快速部署一个Mock Server。</p><p>Moco是一个开源框架,这是它的<a href="https://link.segmentfault.com/?enc=KZT0%2BoUmbWiry%2Fd5WbaXRg%3D%3D.D3Tgd%2BzzTLIgfK7gmD%2FL8IjEI5ZqzqLZs1HTFRPXzMSbeqgbTha2Z6XzY577qu9U" rel="nofollow">Github链接</a>。</p><h4>准备工作</h4><p>首先去Moco的github页面下载准备好的jar文件。</p><p>其次需要自己准备一个定义response的JSON文件,如下。里面的内容需要根据自己的业务去定义要返回的mock数据是什么。</p><pre><code class="jsx">[
{
"response" :
{
"text" : "Hello, Moco"
}
}
]</code></pre><p>最后在云函数中运行需要一个启动文件,文件名必须是<code>scf_bootstrap</code>,内容如下:</p><pre><code class="jsx">#!/bin/bash
/var/lang/java8/bin/java -jar moco-runner-1.2.0-standalone.jar http -p 9000 -c foo.json</code></pre><p>其中端口号必须是<code>9000</code>,JSON配置文件名如果不是foo.json则需要改成自己的文件名。</p><p>然后把这个三个文件打包成一个zip文件,如下图。<br><img src="/img/bVcXslE" alt="截屏2021-12-29 16.58.53.png" title="截屏2021-12-29 16.58.53.png"></p><h4>部署Mock Server</h4><p>打开云函数的控制台,新建一个云函数。如下图。</p><ul><li>选择<strong>从头开始</strong></li><li>选择<strong>Web函数</strong></li><li>运行环境选择<strong>Java8</strong></li><li>在函数代码那里上传刚才打包好的zip文件<br><img src="/img/bVcXslG" alt="Untitled.png" title="Untitled.png"></li></ul><p>最后,点击完成即可。</p><p>然后,你到函数管理界面就可以看到访问路径了。如下图。</p><p>向URL发送HTTP请求就能获得你在JSON文件中定义的response。<br><img src="/img/bVcXslV" alt="1.png" title="1.png"></p><h4>一键部署</h4><p>上面的方式是不是已经很快捷了。但是还有更快的,没有错!</p><p>Mock server已经上架到云函数的官方模版中了。</p><p>如下图,在模版中搜索mock就可以看到,一键就可以部署一个Mock server了。<br><img src="/img/bVcXslZ" alt="34.png" title="34.png"></p><h2>用Serverless搭建Mock Server的优势</h2><p>用Serverless搭建Mock Server具有下面几个优势。</p><h4>快速搭建</h4><p>所有开发团队都希望只花极少的时间就能快速搭建一个Mock Server。</p><p>因此使用Serverless不用关注和维护服务器,所以可以快速搭建运行一个mock server。</p><h4>极低成本</h4><p>由于Mock server只用于测试,如果我们购买服务器来搭建,会增加不少金钱成本和维护成本。</p><p>而Serverless按量收费和免运维的特点,则可以既节约了金钱成本,又节约了维护成本。</p><p>通常我们调用Mock Server的次数都很少,而云函数是按调用次数收费的,每个月有10万次免费调用次数。所以使用云函数则可以免费薅羊毛。</p><h4>无需运维</h4><p>我们不需要像管理服务器那样需要去配置端口、防火墙等。</p><p>只需要上传mock server就结束了。</p><h2>最后</h2><p>Serverless还可以做很多类似的事情,因为它的高性能、自动伸缩、按量计费等特性,让它成为了很多解决方案中的性价比首选。</p><p>未来我会继续探索serverless的更多实用的场景与大家分享。</p>
Serverless与微服务探索(二)- SpringBoot项目部署实践
https://segmentfault.com/a/1190000041013292
2021-11-24T21:23:19+08:00
2021-11-24T21:23:19+08:00
Woody
https://segmentfault.com/u/woodyyan
0
<h2>前言</h2><p>上次的文章分享后,有粉丝反应内容太理论太抽象,看不到实际的样子。</p><p>因此,我这里就写一篇教程,手把手教你如何把一个SpringBoot项目部署到Serverless并测试成功。</p><p>下面的链接是我发表到官方的文章,但官方的文章会综合考虑,所以不会有那么细的步骤。本文是最详细的步骤。</p><p><a href="https://link.segmentfault.com/?enc=ttM7s%2FNENlkM1TWUUzvIlw%3D%3D.sDRBz5lwvuljrn1KiYu7YTFwUfOeeqoGqF2OotML%2Bpq4sf%2F38mAukS5ePxTZZS9O2YfKRDRnzI8Nt2s%2FRd26VQ%3D%3D" rel="nofollow">SpringBoot + SCF 最佳实践:实现待办应用</a></p><p>本文章以腾讯云Serverless云函数为例,将分为事件函数和Web函数两种教程。</p><p>事件函数就是指函数是由事件触发的。</p><p>Web函数就是指函数可以直接发送HTTP请求触发函数。具体区别可以看<a href="https://link.segmentfault.com/?enc=J7m7%2FhDCu9b7sRs7p6Sqjw%3D%3D.MKV1vrTn5fSqCNBl%2BBMQ%2F68c3ZJpQFQWZmurcWIcJ896ad5jw7c2td6sNOBOBbMehqxaTirmu7zXWQWDI2xbaQ%3D%3D" rel="nofollow">这里</a>。</p><p>两者在Spring项目迁移改造上的区别在于:</p><ul><li>事件函数需要增加一个入口类。</li><li>Web函数需要修改端口为固定的9000。</li><li>事件函数需要操作更多的控制台配置。</li><li>Web函数需要增加一个scf_bootstrap启动文件,和不一样的打包方式。</li></ul><h2>事件函数</h2><h3>Spring项目准备</h3><p>事件函数示例代码下载地址:<a href="https://link.segmentfault.com/?enc=v4OsapM6e7pLWRkmJ3sh4Q%3D%3D.PctZVrguIMw1tWDHVJY%2BQm1j%2F5Tn3A%2BYX%2BsfppVaeupCtU1ggcJd%2FnHdW02nI5dw13fGyzlmQksMs1RgzAwjTdZwWkc%2FGFc4TmgeDA66ZsU%3D" rel="nofollow">https://github.com/woodyyan/scf-springboot-java8/tree/eventfunction</a></p><h4>示例代码介绍</h4><p><code>@SpringBootApplication</code> 类保持原状不变。</p><pre><code class="java">package com.tencent.scfspringbootjava8;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ScfSpringbootJava8Application {
public static void main(String[] args) {
SpringApplication.run(ScfSpringbootJava8Application.class, args);
}
}</code></pre><p>Controller类也会按照原来的写法,保持不变。这里以todo应用为例子。</p><p>记住此处的<code>/todos</code> 路径,后面会用到。</p><p>代码如下:</p><pre><code class="groovy">package com.tencent.scfspringbootjava8.controller;
import com.tencent.scfspringbootjava8.model.TodoItem;
import com.tencent.scfspringbootjava8.repository.TodoRepository;
import org.springframework.web.bind.annotation.*;
import java.util.Collection;
@RestController
@RequestMapping("/todos")
public class TodoController {
private final TodoRepository todoRepository;
public TodoController() {
todoRepository = new TodoRepository();
}
@GetMapping
public Collection<TodoItem> getAllTodos() {
return todoRepository.getAll();
}
@GetMapping("/{key}")
public TodoItem getByKey(@PathVariable("key") String key) {
return todoRepository.find(key);
}
@PostMapping
public TodoItem create(@RequestBody TodoItem item) {
todoRepository.add(item);
return item;
}
@PutMapping("/{key}")
public TodoItem update(@PathVariable("key") String key, @RequestBody TodoItem item) {
if (item == null || !item.getKey().equals(key)) {
return null;
}
todoRepository.update(key, item);
return item;
}
@DeleteMapping("/{key}")
public void delete(@PathVariable("key") String key) {
todoRepository.remove(key);
}
}</code></pre><p>增加一个<code>ScfHandler</code>类,项目结构如下:<br><img src="/img/bVcWfzC" alt="截屏2021-11-09 21.31.31.png" title="截屏2021-11-09 21.31.31.png"></p><p><code>Scfhandle</code>类主要用于接收事件触发,并转发消息给Spring application,然后接收到Spring application的返回后把结果返回给调用方。</p><p>默认端口号为<code>8080</code>.</p><p>其代码内容如下:</p><pre><code class="java">package com.tencent.scfspringbootjava8;
import com.alibaba.fastjson.JSONObject;
import com.qcloud.services.scf.runtime.events.APIGatewayProxyRequestEvent;
import com.qcloud.services.scf.runtime.events.APIGatewayProxyResponseEvent;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import java.util.HashMap;
import java.util.Map;
public class ScfHandler {
private static volatile boolean cold_launch;
// initialize phase, initialize cold_launch
static {
cold_launch = true;
}
// function entry, use ApiGatewayEvent to get request
// send to localhost:8080/hello as defined in helloSpringBoot.java
public String mainHandler(APIGatewayProxyRequestEvent req) {
System.out.println("start main handler");
if (cold_launch) {
System.out.println("start spring");
ScfSpringbootJava8Application.main(new String[]{""});
System.out.println("stop spring");
cold_launch = false;
}
// 从api geteway event -> spring request -> spring boot port
// System.out.println("request: " + req);
// path to request
String path = req.getPath();
System.out.println("request path: " + path);
String method = req.getHttpMethod();
System.out.println("request method: " + method);
String body = req.getBody();
System.out.println("Body: " + body);
Map<String, String> reqHeaders = req.getHeaders();
// construct request
HttpMethod httpMethod = HttpMethod.resolve(method);
HttpHeaders headers = new HttpHeaders();
headers.setAll(reqHeaders);
RestTemplate client = new RestTemplate();
HttpEntity<String> entity = new HttpEntity<>(body, headers);
String url = "http://127.0.0.1:8080" + path;
System.out.println("send request");
ResponseEntity<String> response = client.exchange(url, httpMethod != null ? httpMethod : HttpMethod.GET, entity, String.class);
//等待 spring 业务返回处理结构 -> api geteway response。
APIGatewayProxyResponseEvent resp = new APIGatewayProxyResponseEvent();
resp.setStatusCode(response.getStatusCodeValue());
HttpHeaders responseHeaders = response.getHeaders();
resp.setHeaders(new JSONObject(new HashMap<>(responseHeaders.toSingleValueMap())));
resp.setBody(response.getBody());
System.out.println("response body: " + response.getBody());
return resp.toString();
}
}</code></pre><h4>Gradle</h4><p>这里以gradle为例,与传统开发不一样的地方主要在于,<code>build.gradle</code>中需要加入全量打包的plugin,来保证所有用到的依赖都打入jar包中。</p><ol><li>添加<code>id 'com.github.johnrengelman.shadow' version '7.0.0'</code> 这个plugin。</li><li>添加<code>id 'application'</code></li><li>添加<code>id 'io.spring.dependency-management' version '1.0.11.RELEASE'</code></li><li>指定<code>mainClass</code>。</li></ol><p><code>build.gradle</code>具体内容如下:</p><pre><code class="groovy">plugins {
id 'org.springframework.boot' version '2.5.5'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java-library'
id 'application'
id 'com.github.johnrengelman.shadow' version '7.0.0'
}
group = 'com.tencent'
version = '0.0.2-SNAPSHOT'
sourceCompatibility = '1.8'
repositories {
mavenCentral()
}
dependencies {
api 'org.springframework.boot:spring-boot-starter-web'
api group: 'com.tencentcloudapi', name: 'tencentcloud-sdk-java', version: '3.1.356'
api group: 'com.tencentcloudapi', name: 'scf-java-events', version: '0.0.4'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
test {
useJUnitPlatform()
}
application {
// Define the main class for the application.
mainClass = 'com.tencent.scfspringbootjava8.ScfSpringbootJava8Application'
}</code></pre><h4>Maven</h4><p>这里以maven为例,与传统开发不一样的点主要在于,pom.xml需要加入<code>maven-shade-plugin</code> ,来保证所有用到的依赖都打入jar包中。同时需要指定<code>mainClass</code>,下面代码中的<code>mainClass</code>需要改为你自己的<code>mainClass</code>路径。</p><p><code>pom.xml</code>具体内容如下:</p><pre><code class="bash"><?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>1.0</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<!-- Build an executable JAR -->
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<mainClass>com.mypackage.MyClass</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
</dependencies>
<configuration>
<keepDependenciesWithProvidedScope>true</keepDependenciesWithProvidedScope>
<createDependencyReducedPom>true</createDependencyReducedPom>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.handlers</resource>
</transformer>
<transformer
implementation="org.springframework.boot.maven.PropertiesMergingResourceTransformer">
<resource>META-INF/spring.factories</resource>
</transformer>
<transformer
implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.schemas</resource>
</transformer>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
</transformers>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
</project></code></pre><h4>编译JAR包</h4><p>下载代码之后,到该项目的根目录,运行编译命令:</p><ul><li>Gradle项目运行:<code>gradle build</code></li><li>Maven项目运行:<code>mvn package</code></li></ul><p>编译完成后就能在当前项目的输出目录找到打包好的jar包。</p><ul><li>Gradle项目:在<code>build/libs</code>目录下看到打包好的jar包,这里需要选择后缀是<code>-all</code>的JAR包。如下图。</li><li>Maven项目:在<code>target</code>目录下能看到打包好的jar包,这里需要选择前缀<strong>不带</strong><code>orginal-</code>的jar包。</li></ul><p>一会部署函数的时候就用这个JAR包。</p><h3>云函数准备</h3><h4>云函数创建</h4><p>在函数服务中,点击新建,开始创建函数。</p><p>如下图</p><ol><li>选择自定义创建</li><li>选择事件函数</li><li>输入一个函数名称</li><li>运行环境选择Java8</li><li>提交方法选择本地上传zip包</li><li><p>执行方法指定为<code>包名.类名::入口函数名</code></p><ol><li>比如此处是:<code>com.tencent.scfspringbootjava8.ScfHandler::mainHandler</code></li></ol></li><li>上传那里选择前面编译好的带<code>-all</code>后缀的jar包。<br><img src="/img/bVcWfzD" alt="截屏2021-11-09 21.39.35.png" title="截屏2021-11-09 21.39.35.png"></li></ol><p>然后点击完成创建函数。</p><h4>云函数配置</h4><p>创建完成之后,选择函数管理-函数配置-编辑。如下图。<br><img src="/img/bVcWfzE" alt="Untitled.png" title="Untitled.png"></p><p>点开编辑之后,在环境配置中:</p><ol><li>把内存修改为1024MB</li><li>把执行超时时间修改为15秒<br><img src="/img/bVcWfzF" alt="截屏2021-11-10 14.25.57.png" title="截屏2021-11-10 14.25.57.png"></li></ol><h4>触发器配置</h4><p>在触发管理中,创建触发器。<br><img src="/img/bVcWfzG" alt="1235.png" title="1235.png"></p><p>创建触发器时,在下图中:</p><ol><li>触发方式选择API网关触发。</li><li>集成响应勾选。</li><li>然后提交<br><img src="/img/bVcWfzH" alt="5325.png" title="5325.png"></li></ol><p>创建完成之后需要修改一些API网关参数。点击API服务名进入修改。<br><img src="/img/bVcWfzI" alt="235.png" title="235.png"></p><p>点击右侧的编辑按钮修改。<br><img src="/img/bVcWfzJ" alt="23.png" title="23.png"></p><p>第一个前端配置中,将路径修改为Spring项目中的默认路径。如下图。<br><img src="/img/bVcWfzK" alt="55.png" title="55.png"></p><p>然后点击立即完成。</p><p>然后点击发布服务。<br><img src="/img/bVcWfzM" alt="截屏2021-11-09 21.54.29.png" title="截屏2021-11-09 21.54.29.png"></p><p>发布完成之后回到云函数控制台。</p><h3>开始测试</h3><p>此处我们就以Controller里面写的第一个<code>GET</code>方法为例,如下图,我们将获得所有的todo items。<br><img src="/img/bVcWfzN" alt="2345.png" title="2345.png"></p><p>在函数管理中,选择函数代码,就可以很方便的进行测试。如下图。</p><ol><li>测试事件选择“API Gateway事件模版”。</li><li>请求方式选择<code>GET</code></li><li>Path填<code>/todos</code></li><li>最后就可以点击测试按钮。<br><img src="/img/bVcWfzQ" alt="4443.png" title="4443.png"></li></ol><p>测试结果和日志将直接显示在界面的右下方。如下图。<br><img src="/img/bVcWfzR" alt="2222.png" title="2222.png"></p><p>如果想要获取完整的访问URL,可以在触发管理中,找到刚才创建的API网关触发器,下面有可以访问的URL。URL后面有复制按钮。如下图。<br><img src="/img/bVcWfzT" alt="111.png" title="111.png"></p><hr><h2>Web函数</h2><h3>Spring项目准备</h3><h4>示例代码介绍</h4><p>Web函数示例代码下载地址:<a href="https://link.segmentfault.com/?enc=ed4mNzGKXfSO7VsjA%2Fjr8Q%3D%3D.E8QbRN0tggjD1Uu8fTgQufTOPU25wgpg9YqKP92K5pkMRD78BEBlrV1jN8lfkQtet8y2yxQOUjkRK9i%2BKphtAMIl3zHv1hX0rNZOYPLuTFQ%3D" rel="nofollow">https://github.com/woodyyan/scf-springboot-java8/tree/webfunction</a></p><p>Web函数的项目代码相比事件函数更简单。代码改造成本几乎没有。对原代码的修改只有一个端口号。</p><p>Web函数则不需要<code>ScfHandler</code>入口类,项目结构如下:<br><img src="/img/bVcWfzU" alt="666.png" title="666.png"></p><p>因为web函数必须保证项目监听端口为<code>9000</code>,所以需要将Spring监听的端口改为9000。如下图:<br><img src="/img/bVcWfzV" alt="111235.png" title="111235.png"></p><h4>代码部署包准备</h4><p>代码包编译方式参考上面的“<strong>编译JAR包</strong>”。</p><p>然后新建一个<a href="https://link.segmentfault.com/?enc=PxDsry8rf0JyOXYZLi%2BQYQ%3D%3D.S77yxqcop2fiyNbP80aTzaw%2B1IVjQq8gy8t%2FqyACXLycJ0Dl5LG6LCZccN1%2FOyUfB%2FPzsNi0AZt8%2BUivTxhZOw%3D%3D" rel="nofollow">scf_bootstrap启动文件</a>,文件名字必须是<code>scf_bootstrap</code>,没有后缀名。</p><ol><li>第一行需有 <code>#!/bin/bash</code>。</li><li>java启动命令必须是绝对路径,java的绝对路径是:<code>/var/lang/java8/bin/java</code></li><li>请确保你的 scf_bootstrap 文件具备777或755权限,否则会因为权限不足而无法执行。</li></ol><p>因此启动文件内容如下:</p><pre><code class="bash">#!/bin/bash
/var/lang/java8/bin/java -Dserver.port=9000 -jar scf-springboot-java8-0.0.2-SNAPSHOT-all.jar</code></pre><p>接着,在scf_bootstrap文件所在目录执行下列命令来保证scf_bootstrap文件可执行。</p><pre><code class="bash">chmod 755 scf_bootstrap</code></pre><p>然后将<code>scf_bootstrap</code>文件和刚才编译处理的<code>scf-springboot-java8-0.0.2-SNAPSHOT-all.jar</code>文件,一起打包成zip文件。如下图。</p><p>打包好的zip文件就是我们的部署包。<br><img src="/img/bVcWfAz" alt="截屏2021-11-11 13.38.02.png" title="截屏2021-11-11 13.38.02.png"></p><h3>云函数创建</h3><p>在函数服务中,点击新建,开始创建函数。</p><p>如下图</p><ol><li>选择自定义创建</li><li>选择Web函数</li><li>输入一个函数名称</li><li>运行环境选择Java8</li><li>提交方法选择本地上传zip包</li><li>上传那里选择前面压缩好的<code>scf_spring_boot.zip</code>包。<br><img src="/img/bVcWfAF" alt="截屏2021-11-11 13.40.28.png" title="截屏2021-11-11 13.40.28.png"></li></ol><p>然后在下面的高级配置中,写上启动命令,命令中的jar文件应该是你编译出来的jar文件的名字。</p><p>因为web函数必须保证项目监听端口为<code>9000</code>,所以命令中要指定一下端口。</p><p>更多关于启动命令的写法可以参考<a href="https://link.segmentfault.com/?enc=kg%2BESyWqdA4kNFin9B6NJw%3D%3D.HhpN0WKXPJqTZOaoT47SRDlhkIPWe4g7Eo4Vm1ubRTKxs6Ajmf5L3Vl4A%2FdF2%2F5VgCfwRV98B%2FUEMZxG6PO%2Bww%3D%3D" rel="nofollow">启动文件说明</a>。</p><p>如下图:<br><img src="/img/bVcWfAG" alt="13ewd.png" title="13ewd.png"></p><p>然后环境配置那里,把内存改为512MB。执行超时时间设置为15秒。<br><img src="/img/bVcWfAH" alt="23we.png" title="23we.png"></p><p>其他设置都使用默认的就可以了。然后点击完成。</p><p>点击完成之后如果没有反应,是因为要先等待ZIP文件上传,才会开始创建函数。</p><p>因为Web函数默认会创建API网关触发器,因此我们不需要单独配置触发器。</p><h3>开始测试</h3><p>此处我们就以Controller里面写的第一个<code>GET</code>方法为例,如下图,我们将获得所有的todo items。<br><img src="/img/bVcWfzN" alt="awdz.png" title="awdz.png"></p><p>在函数控制台的函数代码里面,我们可以直接测试我们的云函数。</p><p>依据上面的代码,我们请求方式选择<code>GET</code>,path填写<code>/todos</code>,然后点击测试按钮,然后就可以在右下角看到我们的结果了。</p><p>如果想在其他地方测试,可以复制下图中的访问路径进行测试。<br><img src="/img/bVcWfAK" alt="aeawe.png" title="aeawe.png"></p><h2>最后</h2><p>本教程没有涉及镜像函数,因为镜像部署和原来的部署方式没有差异。项目代码也不需要改造。理论上这是最适合微服务项目的方式。</p><p>下一篇文章中,我就会详细分析Serverless中下面几个话题了。</p><ul><li>Serverless中的服务间调用</li><li>Serverless中的数据库访问</li><li>Serverless中的服务的注册与发现</li><li>Serverless中的服务熔断与降级</li><li>Serverless中的服务拆分</li></ul>
Serverless与微服务探索(一)- 如何用serverless实践Spring boot项目
https://segmentfault.com/a/1190000040946724
2021-11-11T21:28:15+08:00
2021-11-11T21:28:15+08:00
Woody
https://segmentfault.com/u/woodyyan
0
<h2>前言</h2><p>随着技术的发展,我们有越来越多的选择来实现我们的业务逻辑。Serverless作为时下前沿的技术,是不是也可以探索一下微服务架构的新可能性?</p><p>这篇文章就是总结近段时间以来,我探索的用serverless落地SpringBoot微服务项目的一些成果。</p><h2>什么是Serverless</h2><p>什么是微服务和什么是springBoot已经不需要我讲解了。</p><p>那什么是Serverless呢?</p><p>根据CNCF的定义,Serverless(无服务器)是指构建和运行不需要服务器管理的应用程序的概念。</p><p>Serverless并不是没有服务器就能进行计算,而是指对于开发者或者公司来说,无需了解和管理底层服务器,就能进行计算。</p><p>通俗一点讲,Serverless就是封装了底层计算资源,你只需要提供函数,就可以运行了。</p><p><img src="/img/bVcVYgV" alt="截屏2021-09-29 14.50.07.png" title="截屏2021-09-29 14.50.07.png"></p><p>这里还要提到一个概念,就是FaaS(Function as a Service),函数即服务。我们通常运行在Serverless上的逻辑是函数级别的粒度。</p><p>因此对于拆分粒度控制很合理的微服务,是非常适合使用serverless的。</p><h4>Serverless对于微服务的价值</h4><ol><li>每个微服务API被调用的频率不一样,可以利用Serverless精准管理成本和弹性。</li><li>不用担心一个API调用量大而需要扩容整个服务。Serverless可以自动扩缩容。</li><li>不需要去运维每个服务背后部署多少个容器,多少个服务器,不用做负载均衡。</li><li>屏蔽了K8S等容器编排的复杂学习成本。</li><li>Serverless这种无状态的特性也非常符合微服务使用Restful API的特性。</li></ol><h2>初步实践</h2><p>首先,需要准备一个SpringBoot项目,可以通过<a href="https://link.segmentfault.com/?enc=R6DQ3jrJvxGKCYDpxrOrBA%3D%3D.u7hBF05e7LYaHhU1kV5SqIqZeUQ22zaNHTQ0pDA6qMA%3D" rel="nofollow">start.spring.io</a>快速创建一个。</p><p>在业务开发上,Serverless和传统的微服务开发并没有任何不同。所以我就快速写了一个todo后端服务,完成了增删改查功能。</p><p>示例代码在<a href="https://link.segmentfault.com/?enc=xXjl4vN7ifMOxWfubWnR9A%3D%3D.InDgTcs1MVzFmM%2FH0%2FbXrzqqZF5MLFm%2FRHbnOVkHZiTxwIDSr0U4%2FAL5pZBfU33zUthrq9vNtVS%2Bm98knOCd4Q%3D%3D" rel="nofollow">这里</a>。</p><p>那么使用Serverless真正有差异的地方在哪里呢?</p><p>如果只是简单的想要部署单个服务,那么主要差异在于两个方面:</p><ol><li>部署方式</li><li>启动方式</li></ol><h4>部署方式</h4><p>由于我们摸不到服务器了,所以部署方式的变化是很大的。</p><p>传统的微服务部署,通常是直接部署到虚拟机上运行,或者用K8S做容器化的调度。</p><p>传统的部署关系大致如下图。<br><img src="/img/bVcVYgW" alt="截屏2021-11-11 17.19.48.png" title="截屏2021-11-11 17.19.48.png"></p><p>如果使用serverless通常要求我们的微服务拆分粒度更细,才能做到FaaS。<br>所以使用Serverless部署微服务的关系大致如下图。<br><img src="/img/bVcVYgX" alt="截屏2021-11-11 17.27.47.png" title="截屏2021-11-11 17.27.47.png"></p><p>Serverless只需要提供代码就可以了,因为serverless自带运行环境,因此serverless部署微服务通常有两种方式:</p><ol><li>代码包上传部署</li><li>镜像部署</li></ol><p>第一种方式和传统部署相比是差异最大的。它需要我们将写好的代码打包上传。并且需要指定一个入口函数或者指定监听端口。</p><p>第二个种方式和传统的方式相比几乎不变,都是把做好的镜像上传到我们的镜像仓库。然后在serverless平台部署的时候选择对应的镜像。</p><h4>启动方式</h4><p>因为serverless是使用的时候才会创建对应的实例,不使用的时候就会销毁实例,体现了serverless按量计费的特点。</p><p>所以serverless在第一次调用的时候存在一个冷启动的过程。所谓冷启动就是指需要平台分配计算资源、加载并启动代码。因此依据不同的运行环境和代码可能有不同的冷启动时间。</p><p>而Java作为一种静态语言,它的启动速度也一直被人诟病。然而还有更慢的,就是spring的启动时间,是大家有目共睹的慢。所以,java+spring这种强强联合造就了树懒般的启动速度。就有可能造成首次调用服务出现超长的等待时间。</p><p>不过,不用担心,spring已经提供了两种解决方案来缩短启动时间。</p><ol><li>一种是<a href="https://link.segmentfault.com/?enc=pQ8Rklt063OgNUbLPVTDrQ%3D%3D.6llUBvLKYElaZEtMneyA5gdzDbTWtxjRObOFRq2acpUZYabwBOQ57teOsOgJDpP79IR56ah0oXB3sDFvkazQcQ%3D%3D" rel="nofollow">SpringFu</a></li><li>另一种是<a href="https://link.segmentfault.com/?enc=h0EEePNaFk8GJfPKRCVPSA%3D%3D.I77h2iTIrkcAbEhDBoLF0oa0L1OkU3AfABAkVr84TJjNjL2wIs1bPYhHS6V4KGY2U%2BuxHnewm9MDIJu9wjKTuA%3D%3D" rel="nofollow">Spring Native</a>。</li></ol><p><strong>SpringFu</strong></p><p>Spring Fu 是 JaFu (Java DSL) 和 KoFu (Kotlin DSL) 的孵化器,以声明式方式使用代码显式配置 Spring Boot,由于自动完成,具有很高的可发现性。 它提供快速启动(比最小 Spring MVC 应用程序上的常规自动配置快 40%)、低内存消耗,并且由于其(几乎)无反射方法非常适合 GraalVM 本机。如果搭配上GraalVM编译器,应用启动速度就能直线下降到原先的大约1%。</p><p>不过,目前SpringFu还处于特别早期的阶段,使用过程中问题也比较多。另外,使用SpringFu会有较大的代码改造成本,因为它干掉了所有的annotation,所以这次我没有使用SpringFu的方式。</p><p><strong>Spring Native</strong></p><p>Spring Native 为使用 GraalVM native-image编译器将 Spring 应用程序编译为native可执行文件,以提供打包在轻量级容器中的native部署选项。 Spring Native的目标是在这个新平台上支持几乎没有代码改造成本的 Spring Boot 应用程序。</p><p>因此我选择了Spring native,因为它不需要改造代码,只需要添加一些插件与依赖就能实现native image。</p><p>Native image有几大好处:</p><ol><li>在构建时会移除未使用的代码</li><li>classpath 在构建时就已经确定</li><li>没有类延迟加载:可执行文件中所有的内容都会在启动时加载到内存中</li><li>在构建时就运行了一些代码</li></ol><p>基于这些特性,因此它能让程序的启动时间大大加快。</p><p>关于如何使用它我将在下一篇文章中讲解,详细教程可以查看这个<a href="https://link.segmentfault.com/?enc=LW4OfZxVxUyRD%2FjUZLi73A%3D%3D.lsO%2BDMUEKcBkT6oCj9B52ZjCZC1vL%2B6hYHxm8aAn1cJyST60gH5CfoF9QVLFYjuN4Aig157NqVcHiDrLdBgxpFY3Jwm1xjm0guIPQlXfXrw%3D" rel="nofollow">官方教程</a>。我也是参考这个教程做的。</p><p>我就说说我的测试对比结果吧。</p><p>我把编译好的image分别在本地,腾讯云serverless的云函数和AWS serverless lambda进行了部署和测试。</p><table><thead><tr><th>规格</th><th>SpringBoot冷启动时长</th><th>SpringNative冷启动时长</th></tr></thead><tbody><tr><td>本地16G内存Mac</td><td>1秒</td><td>79毫秒</td></tr><tr><td>腾讯云Serverless 256M内存</td><td>13秒</td><td>300毫秒</td></tr><tr><td>AWS Serverless 256M内存</td><td>21秒</td><td>1秒</td></tr></tbody></table><p>从测试结果看,SpringNative大大提升了启动速度。提高serverless的规格还能进一步提升速度。</p><p>如果Serverless的冷启动速度控制到了1秒内,那么大部分业务都是能接受的。并且也只有首次请求的时候会存在冷启动的情况,其他请求都和普通的微服务响应时间一样。</p><p>此外,目前各大平台的Serverless都支持预置实例,也就是在访问到来之前提前创建实例来减少冷启动时间。带来业务上更高的相应时间。</p><h2>总结</h2><p>Serverless作为目前先进的技术,它给我们带来诸多好处。</p><ol><li>自动扩缩容的弹性和并发性</li><li>细粒度的资源分配</li><li>松耦合</li><li>免运维</li></ol><p>但serverless也不是完美的,当我们尝试在微服务领域使用它的时候,我们依然能看到它存在很多问题等待解决。</p><ol><li><p>难以监视和调试</p><ol><li>这是目前公认的一个痛</li></ol></li><li><p>可能会有更多的冷启动</p><ol><li>当我们拆分微服务为了适应函数粒度时,同时也分散了每个函数的调用时间,导致每个函数调用频率变低,带来更多的冷启动。</li></ol></li><li><p>函数间的交互会更复杂</p><ol><li>由于函数粒度变细,在大型微服务项目中,导致原本就错综复杂的微服务会变得更加错综复杂。</li></ol></li></ol><p>总结起来就是,想要完全替代传统的虚拟机,在微服务这条路上Serverless还有很长的路要走。</p><h2>下一步</h2><p>我会继续探索serverless与微服务的实践。</p><p>后面的文章我会探讨下面几个话题</p><ul><li>Serverless中的服务间调用</li><li>Serverless中的数据库访问</li><li>Serverless中的服务的注册与发现</li><li>Serverless中的服务熔断与降级</li><li>Serverless中的服务拆分</li></ul>
云基础设施架构设计
https://segmentfault.com/a/1190000040136160
2021-06-07T17:31:45+08:00
2021-06-07T17:31:45+08:00
Woody
https://segmentfault.com/u/woodyyan
0
<h2>前言</h2><p>随着AWS、阿里云、Azure、腾讯云等公有云的蓬勃发展,越来越多的企业开始在考虑上公有云了。</p><p>这些年做架构咨询,发现很多传统的公司在基础设施上云的过程中没有明确的设计思路,只是一通购买云产品,然后使用云产品,认为这样就是上云了。</p><p>但其实,如果没有一个很好的云基础设施架构设计,会使后续的云使用变得难以维护,达不到预期的效果,同时成本上升。</p><p>这篇文章里面,我会分享一些过去项目中的公有云设计经验和思路,给大家提供一些基于微服务的场景下如何设计云基础设施架构的参考。其中这里的云指的是如阿里云,AWS的公有云。</p><hr><h2>为什么要上云</h2><p>上云的好处或价值在各大文章里面已经说得非常多了。</p><p>我这里也敷衍的列举几个好处和价值吧。</p><ol><li>降低成本</li><li>可伸缩性</li><li>专业的运维</li><li>速度</li></ol><p><strong>降低成本</strong></p><p>降低成本主要是降低了两类成本。</p><ul><li>公司不再需要招聘专业的运维人员专门负责维护服务器。(大型公司除外,他们通常需要大量的运维人员统一维护全公司的云资产或者自建云计算)</li><li>公司不再需要专门的人员来针对运维开发各类工具,如今常见的云计算平台都包含丰富的功能,以及完善的售后体系。</li></ul><p><strong>可伸缩性</strong></p><p>可伸缩是传统的服务器无法做到的,这也正是如今云计算越来越火的一个很大的原因。</p><p>我们可以根据业务的需要,扩展服务性能,不仅如此,而且还能做到缩小服务性能,以节约成本。</p><p><strong>专业的运维</strong></p><p>上云之后不是没有运维了,而是把运维交给了云计算平台的专业的人来做了。而你只需要关心如何基于这些云基础设施构建自己的产品了。</p><p><strong>速度</strong></p><p>速度是指各方面的速度都提升了。比如,你只需要花1分钟时间就能创建一台新的服务器,你只需要花1分钟时间就能扩容某个服务。由于减少了各种搭建配置时间,开发时间因此也缩短了。缩短了试验和测试的时间,更快的为客户提供可用性。</p><hr><h2>云基础设施架构成熟度评估</h2><p>那么上云之后我们如何知道我们的云基础设施架构是足够优秀的呢?</p><p>这里就需要有一套云基础设施架构成熟度评估模型。</p><p>我根据这些年的架构咨询工作,结合多个项目总结了这套云基础设施架构成熟度评估模型。它</p><p>主要分为8个模型,5种等级。</p><p>这8个模型是:</p><ol><li><strong>可伸缩性</strong> - 云基础设施能根据业务需要自由伸缩</li><li><strong>可复制性</strong> - 云基础设施能根据业务需要快速复制</li><li><strong>可恢复性</strong> - 云基础设施挂了之后能自动或快速恢复</li><li><strong>可用性</strong> - 云基础设施的设计能保证服务的高可用性</li><li><strong>安全性</strong> - 云基础设施的设计能有非常高的安全设计</li><li><strong>可量化管理</strong> - 云基础设施应该可以被量化管理以优化成本</li><li><strong>可维护性</strong> - 云基础设施应该具有更简单的可维护性</li><li><strong>可组合性</strong> - <em>*</em>*云基础设施会根据业务需要组合使用</li></ol><p>这5种等级是:</p><ol><li><strong>原始级</strong> - 完全没有使用云基础设施</li><li><strong>基础级</strong> - 尝试了一些基本的云基础设施</li><li><strong>标准级</strong> - 所有基础设施都上云了</li><li><strong>成熟级</strong> - 所有基础设施都上云了并且掌握云基础设施架构的最佳实践</li><li><strong>领先级</strong> - 自建云计算</li></ol><p>结合上面的模型,我们就可以得出如下的打分。</p><p><img src="/img/bVcSzoe" alt="Screen_Shot_2021-06-07_at_11.50.21.png" title="Screen_Shot_2021-06-07_at_11.50.21.png"></p><p>基于这个打分,我们就可以得到如下的评估图。</p><p><img src="/img/bVcSzoh" alt="Screen_Shot_2021-06-07_at_11.55.58.png" title="Screen_Shot_2021-06-07_at_11.55.58.png"></p><p>那么接下来,我们就把这个架构设计展开来说一说。</p><p>主要说一说VPC设计,访问控制设计,安全设计和数据库设计。</p><hr><h2>VPC设计</h2><p>VPC全称是Virtual Private Cloud,是云上的一个逻辑隔离的专有网络。</p><p>使用VPC主要是为了安全隔离,把不同的环境隔离开来。一是避免环境污染,二是保障安全性。</p><p>因此,如下图所示,通常一个企业都会设计以下几个VPC环境:</p><ol><li>产品环境</li><li>测试环境</li><li>开发环境</li><li>UAT环境</li></ol><p><strong>产品环境</strong>是我们线上所有产品运行的VPC环境,只有用户能接触到的一个环境。</p><p><strong>测试环境</strong>是做测试用的,也是大部分公司开发人员和测试人员能接触的一个环境。</p><p><strong>开发环境</strong>则是日常工作所在的环境,它通常与办公网络是连通的,这里会放我们的git,pipeline,镜像仓库,制品库等。</p><p><strong>UAT环境</strong>通常是给客户做验证的,比如上线前,需要让客户去验证是否符合预期了,这个环境之所以不能使用测试环境是因为通常客户需要导入一些真实数据做测试,需要保证UAT环境的干净。</p><p><img src="/img/bVcSzoj" alt="Screen_Shot_2021-05-27_at_15.39.53.png" title="Screen_Shot_2021-05-27_at_15.39.53.png"></p><p>另外,如果有多地域部署系统的要求,就需要使用多个VPC,因为VPC是地域级别的资源,是不能跨地域的。</p><hr><h2>访问控制设计</h2><p>我们的云资源不是对所有人开放的,特别是对于产品环境的访问控制应该尤其严格,一是防止内部人的误操作,二是防止黑客的入侵。</p><p>但我在很多客户那里都遇到过,他们没有任何访问控制设计,所有的开发人员都共用一个或几个账号。这是非常危险的使用方式,不利于管理,也有诸多风险。</p><p>现在的公用云都有访问控制功能,通常叫做Resource Access Management。</p><p>访问控制的设计主要从下面几个纬度去考虑:</p><ol><li><p>用户管理</p><ol><li>用户分为:真实用户,虚拟用户</li><li>真实用户就是那些真实的人。比如员工和用户。</li><li>虚拟用户就是分配给某个系统使用的账号。比如某系统需要有上传图片的权限。</li></ol></li><li><p>读写分离</p><ol><li>通常有的人只应该拥有只读权限。</li><li>有的提供给系统的账号应该只有只读权限。</li><li>比如,访问对象存储的用户头像的账号应该只有只读权限。</li><li>管理员或者上传图片功能需要拥有写入权限。</li></ol></li><li><p>角色管理</p><ol><li>不同的人可能都是同一个角色。</li><li>同一个人可能拥有不同角色。</li><li>角色决定了我们拥有哪些权限集。</li></ol></li></ol><p>其次就是,云基础设施的访问控制是否需要和企业内部自己的单点登录集成,也是需要考虑的设计。</p><p>总结一下就是,访问控制应该遵循最小权限原则,才能最大限度的保证系统的安全性。</p><hr><h2>安全设计</h2><p>大部分人的误区是,我已经用云了,再买个防火墙什么的,就很安全了。</p><p>但其实公有云是完全暴露在互联网的,因此也需要有完善的安全设计才能保证云基础设施的安全。</p><p>把该隐藏的隐藏进私网里面,只暴露最少的信息。</p><p>基础设施的安全设计主要包含几个方面:</p><ol><li><p>网络安全</p><ol><li>包括传输安全,比如数据如何加密传输</li><li>网络是否暴露</li><li>网络设计是否合理</li></ol></li><li><p>数据安全</p><ol><li>数据是否被足够的保护了</li><li>数据是否暴露在了外面</li></ol></li><li><p>权限安全</p><ol><li>是否按照最小权限设计的</li><li>是否读写权限分离了</li></ol></li></ol><p><img src="/img/bVcSzoN" alt="Screen_Shot_2021-06-04_at_15.50.02.png" title="Screen_Shot_2021-06-04_at_15.50.02.png"></p><p>总结一下,设计的时候需要遵循两个原则:</p><ol><li>零信任网络</li><li>最小权限设计</li></ol><hr><h2>数据库设计</h2><p>这里主要是涉及到数据库的高可用和高性能的设计问题。</p><p>高可用方案通常有3种方向:</p><ol><li><p>主备架构</p><ol><li>通常会有多节点,不同点节点会在不同的可用区</li></ol></li><li><p>容灾</p><ol><li>容灾主要分为异地容灾和同城容灾</li></ol></li><li><p>备份恢复</p><ol><li>数据库挂了之后如何快速自动恢复</li><li>恢复期间的数据丢失如何找回</li></ol></li></ol><p>这里的高性能设计不包括分库分表相关的设计,因为这只是关于基础设施的架构设计。</p><p>通常需要考虑:</p><ol><li>如何弹性扩容?</li><li><p>是否需要读写分离?</p><ol><li>读写分离后数据的一致性如何保证?</li><li>根据业务场景,需要多少读实例?</li></ol></li><li>缓存如何设计</li></ol><p>下面是一个大概的高性能高可用数据库架构的样子,供大家参考。</p><p><img src="/img/bVcSzpe" alt="Screen_Shot_2021-06-03_at_17.06.46.png" title="Screen_Shot_2021-06-03_at_17.06.46.png"></p><hr><h2>云基础设施架构设计</h2><p>总结起来,一个常见的基于微服务的云基础架构设计,大概就长下图的样子。</p><p>我们在设计的时候可能需要考虑的远不止下图的中的东西。</p><p>比如我们得考虑:</p><ul><li>多个VPC如何通信</li><li>集群如何编排</li><li>数据库选型</li><li>日志收集用什么工具才能易于收集易于搜索</li><li>运维监控用什么工具才能全面监控并能智能警报</li><li>MQ是否要满足一些特殊场景</li><li>第三方服务是否有特殊要求</li><li>在这个架构下我们是否能动态横向和纵向扩容</li></ul><p><img src="/img/bVcSzpj" alt="Screen_Shot_2021-06-07_at_10.31.54.png" title="Screen_Shot_2021-06-07_at_10.31.54.png"></p><hr><h2>总结</h2><p>希望上面的一些分享能帮助大家在设计云基础设施架构的时候提供一些参考。</p><p>这样的架构设计涉及的东西是非常广的,不同的项目也会有不同的设计要求。</p><p>因此,最终设计一定要结合项目的实际情况,满足了业务需要就是好的设计。</p><p>记住一句话,没有正确的设计,只有刚好适用的设计。</p>
从价值流图分析研发效能
https://segmentfault.com/a/1190000039976969
2021-05-10T18:51:01+08:00
2021-05-10T18:51:01+08:00
Woody
https://segmentfault.com/u/woodyyan
1
<h2>什么是价值流</h2><p><img src="/img/bVcRTZk" alt="Screen_Shot_2021-05-10_at_17.42.16.png" title="Screen_Shot_2021-05-10_at_17.42.16.png"></p><p>传统的工作中,不同的职位都只关注自己所交付的东西,比如产品经理关注产品需求文档的交付,开发人员关注软件代码的交付,运维人员关注于软件产品的部署。</p><p>随着DevOps与敏捷的发展,它们越来越强调交付整体价值,而不是单一角色的交付内容。</p><p>因此,价值流的意义就体现出来了。价值流是DevOps的关键概念之一。</p><p>根据《DevOps精要》的解释,我们可以从创造价值以响应客户请求的角度,来考虑一下组织中的工作。完成请求所需要实施的相关行动,可以按顺序排列起来,这称为价值流。</p><h2>什么是价值流图</h2><p>价值流图就是可视化的价值流。</p><p>它大概长下图的样子。<br><img src="/img/bVcRTZx" alt="Screen_Shot_2021-05-10_at_13.56.20.png" title="Screen_Shot_2021-05-10_at_13.56.20.png"></p><h2>如何画价值流图</h2><p>画价值流图其实很简单:</p><ol><li>识别处理请求的关键步骤</li><li><p>分析每个关键步骤所需的3个度量数据</p><ol><li>前置时间 Lead Time,LT</li><li>处理时间 Process Time,PT</li><li>完成度与准确度百分比 the Percent Complete and Accurate,%C/A</li></ol></li><li>将这些步骤组织为一个创造预期结果的活动序列</li></ol><p>价值流图画好之后,最有价值的信息是流中每个步骤的3个度量数据,即前置时间(Lead Time,LT)、处理时间(Process Time,PT)及 完整度与准确度百分比(the Percent Complete and Accurate,%C/A)。</p><p><strong>前置时间</strong></p><p>前置时间是供应链管理中的一个术语,是指从采购方开始下单订购到供应商交货所间隔的时间。</p><p>对应到我们的软件开发中,则是指一个任务从创建到完成的时间。如下图所示。</p><p><strong>处理时间</strong></p><p>处理时间指的是一个任务从开始做到完成所需的时间。如下图所示。</p><p><img src="/img/bVcRTZJ" alt="Screen_Shot_2021-05-06_at_14.50.17.png" title="Screen_Shot_2021-05-06_at_14.50.17.png"></p><p><strong>完整度与准确度百分比</strong></p><p>一个任务完成了并不意味着它是准确的。</p><p>举个很简单的例子,我们读书的时候写作业,通常我们都能100%的完成,但并不能百分百的正确。</p><p>这在软件开发中也非常常见,一个需求被实现了,但和最初的描述并不一致。</p><p>通过完整度与准确度百分比(%C/A)可以分析项目的返工成本。</p><p><strong>画价值流图的几个tips:</strong></p><ol><li>不要过度细化关键步骤,大致可以按照看板上的列一一对应或略多。</li><li>建议关键步骤数量不要超过15个。</li><li>可以按有堆积或由于等待而产生延迟来画每个步骤。</li><li>实践中,计算这些度量的数值是一个很大的挑战。一个比较可行的做法是取每个迭代的几张卡,然后计算PT的平均值。LT则看它从上一步挪动到下一步等待了多少天,也取平均值。%C/A则难免会拍脑袋得到数值了,这不可避免。</li><li>某个关键会议也可以是步骤之一。比如发布评审会。</li></ol><h2>真实案例分析价值流图</h2><p>下面这张价值流图是来自我一个真实的案例。</p><p><img src="/img/bVcRTZx" alt="Screen_Shot_2021-05-10_at_13.56.20.png" title="Screen_Shot_2021-05-10_at_13.56.20.png"></p><p><strong>对于这个图上的数据我做一些说明:</strong></p><ul><li>不是所有公司的关键流程都长这个样子,公司不同项目不同,画出来的价值流图也不同。</li><li>设计审核的%C/A只有60%是因为当时这个项目的产品经理设计原型的时候并没有频繁和业务方沟通,导致设计的东西审核的时候经常返工。</li><li>软件开发的LT有12天那么长,是因为很多卡ready了之后,需要等待排进某个迭代,所以平均等待时间有7天。这个数字在很多公司可能更长。</li><li>发布申请和发布上线的LT都挺长的,也是因为申请需要等待领导审批,发布需要排期,所以等待时间也挺长。</li><li>用户验收测试的%C/A只有70%也是因为开发过程中并没有频繁获取业务方的反馈,导致用户验收测试的时候发现挺多不符合预期的情况。</li></ul><p><strong>从上图的数据中我们可以得到一些关键信息:</strong></p><ol><li>PT/LT只有39.2%,这就意味着中间有大量的等待时间。那我们就可以具体分析每个等待时间该如何优化。</li><li>构建上传和测试环境部署这两步都没有等待时间,而且%C/A是100%。因为这个案例中它们都是利用pipeline自动化完成的。因此,自动化能帮助我们提高%C/A和缩短等待时间。</li><li>平均%C/A是88.75%,意味着有可以改进的空间。那我们就可以具体分析每个步骤中为什么达不到100%。</li><li>经过分析我们发现,很多%C/A不能达到100%的原因是,这个步骤的人并没有频繁的向前一个步骤获取反馈来验证自己是否做对了。</li><li>经过分析我们发现,很多LT长是因为,有一些审批流程必须等到领导审核才能往下继续走,而领导往往不能及时审批。还有一些LT长是因为没有可视化看板,不同步骤的人并不知道工作已经ready了。</li></ol><p>在上面的例子中,花费在创造预期成果上的工作时间比例, 仅占总开销时间的39.2%。这样的情况在常规IT部门中,类似的占比数字相当普遍,甚至更低。</p><p>上面的例子根据价值流图最终分析产出的报告有很多,这里就不详细展开说了。每个数字都可以研究背后的原因和找到改进的方案。</p><p>有了价值流图之后,通常我们可以提出来这3个问题:</p><ol><li>为什么这些工作步骤的%C/A值低于100%?我们如何才能够完全杜绝错误从一个步骤<br>被传递到下一个步骤(并因返工而浪费时间和资源)?</li><li>除了开发产品的时间,具体有什么因素导致了lead time?我们如何能够大幅降低队列和等<br>待所损耗的时间?</li></ol><p>3、我们如何改变工作实践,来降低每个步骤的处理的时长?</p><h2>值流图的好处</h2><p><img src="/img/bVcRTZO" alt="Screen_Shot_2021-05-10_at_17.42.56.png" title="Screen_Shot_2021-05-10_at_17.42.56.png"></p><ol><li>价值流图的好处在于让参与的团队成员对整个流程有可视化的数据化的认知。并清晰的知道该从哪个步骤入手开始改进。</li><li>其次,过程的可视化呈现,有助于聚焦到被创造的价值上,而不是被实施的动作上。员工们和经理们常常能很好地理解他/她们的日常任务(做什么),而忽视了预期成果(为什么)。</li><li>再次,价值流图有助于识别和消除瓶颈,并避免局部优化的陷阱:即把时间和精力花费在根本没有效果甚至带来负面效果的约束消除上。</li><li>最后,对价值流的了解,有助于实现DevOps的关键思想:构建一个顺畅、一致经各个步骤的价值流,使得我们能够持续地、有节奏地、没有非必要的延迟、并以最优的资源使用方式来交付成果。</li></ol><blockquote>基于Eliyahu Goldratt提出的约束理论,任何系统中,在任何一个时间点上,有且仅有一个真正的瓶颈,这个瓶颈拖慢了工作,同时,花费在除了消除这个瓶 颈点之外的任何事情上的精力,都可以说是浪费。</blockquote><h2>总结</h2><p>要画出完整的价值流图,一定要去研究项目中真实的实践是什么样子的,不能凭空想象,也不要去指望某些记录的文档信息,因为他们常常没人维护。</p><p>价值流图是帮助我们更好的构建DevOps的一种方式,要想做好DevOps只凭这一点是远远不够的。</p><p>如果大家对相关话题感兴趣,可以给我留言,我们一起讨论更多可能性。</p><blockquote><em>参考:《DevOps精要》</em></blockquote>
DevOps成熟度评估模型
https://segmentfault.com/a/1190000039866054
2021-04-21T13:15:35+08:00
2021-04-21T13:15:35+08:00
Woody
https://segmentfault.com/u/woodyyan
2
<h2>什么是DevOps</h2><p><img src="/img/bVcRfhv" alt="" title=""></p><p>随着敏捷软件方法的广泛采用,以及IT基础设施即程序代码的管理方式的推广,DevOps也应运而生了。</p><p>DevOps 是通过人、流程和技术的有机整合,以协作、自动化、精益、度量和共享文化为指引,旨在建立一种可以快速交付价值并且具有持续改进能力的现代化 IT 组织。</p><h2>什么是DevOps成熟度评估</h2><p>随着技术的发展,越来越多的公司期望各种有用的方法论能够标准化,可量化。这样可以帮助决策者快速的知道我目前的水平,以及我未来发展的目标。</p><p>因此,随着DevOps被越来越多的推广,决策者们也期望知道自己公司或者团队的DevOps被量化之后长什么样子。于是DevOps成熟度评估模型便诞生了。</p><h2>DevOps成熟度模型</h2><p>在这些年的咨询生涯中,见过很多公司的成熟度模型。</p><p>这里给大家介绍几种吧。</p><h4>常见DevOps成熟度模型</h4><p>首先是信通院的,信通院把DevOps分成了3个模块,每个模块下面对应了一些纬度。如下:</p><ul><li><p>敏捷开发管理</p><ul><li>价值交付管理</li><li>敏捷过程管理</li><li>敏捷组织模式</li></ul></li><li><p>持续交付</p><ul><li>配置管理</li><li>构建与持续集成</li><li>测试管理</li><li>部署与发布管理</li><li>环境管理</li><li>数据管理</li><li>度量与反馈</li></ul></li><li><p>技术运维</p><ul><li>监控管理</li><li>事件与变更管理</li><li>运营配置管理</li><li>容量与成本管理</li><li>高可用管理</li><li>连续性管理</li><li>用户体验管理</li></ul></li></ul><p>再来看看某技术咨询公司的,他们把DevOps成熟度分为了6个纬度来进行评估。如下:</p><ul><li>组织职能与能力</li><li>轻量级变更流程</li><li>自动化环境管理</li><li>持续部署与发布</li><li>运维监控与度量</li><li>架构解耦</li></ul><p>同样是某咨询公司的,他们的DevOps成熟度模型的纬度又不一样了,他们把成熟度模型分为了8个纬度。如下:</p><ul><li>发布组织与方法论</li><li>精益发布治理与过程</li><li>自动化软件发布</li><li>持续集成</li><li>持续部署</li><li>自动化运维</li><li>基础设施与云计算</li><li>平台与应用架构</li></ul><p>另外一家科技公司,他们的DevOps成熟度模型的纬度也不一样,他们的分法如下:</p><ul><li>持续集成</li><li>持续部署</li><li>轻量级变更流程</li><li>自动化环境管理</li><li>质量保证</li><li>运维监控与度量</li><li>可视化与可追溯</li></ul><h4>我总结的DevOps成熟度模型</h4><p>凭借这些年的DevOps咨询经验,我总结了一套我认为更易用的能符合大部分公司情况的DevOps成熟度模型。结合了各家成熟度模型,做了一些调整和优化,以适用于大部分团队的DevOps成熟度评估。</p><p>他们也是8个纬度,如下:</p><ul><li>组织与文化</li><li>敏捷开发</li><li>CI/CD</li><li>质量与安全</li><li>可视化与自动化</li><li>版本与配置管理</li><li>运维监控与预警</li><li>持续度量与改进</li></ul><p><strong>组织与文化</strong></p><p>DevOps不是一个软件产品,DevOps也不是一个工程师。DevOps需要文化与组织的变化,不仅是开发与运维之间的隔阂需要消失,IT与业务之间的隔阂也需要消失。</p><p>由于DevOps和敏捷一样,离不开组织的变革和支持,同时DevOps也是一种文化,因此综合了一下,把组织能支持DevOps的程度,与现阶段文化与DevOps的匹配程度,作为了这个纬度的关键。</p><p><strong>敏捷开发</strong></p><p>为什么要有这个纬度可能有些人会比较疑惑。因为Oleg Skrynnik在《DevOps精要》里面提到DevOps发展的其中一个前提是敏捷开发被广泛的采用。因此,敏捷做得好不好直接影响到DevOps做得好不好。他们是相辅相成的。</p><p><strong>CI/CD</strong></p><p>CI/CD即代表的是持续集成和持续部署。也可以理解为我们俗称的pipeline流水线。但它指代的不仅仅是工具,更是一种方法论。相对于集成与部署,更重要的是持续两个字。CI/CD占据了我们开发过程中的大部分阶段,从代码提交那一刻开始,到代码运行在生产环境,都是由CI/CD促成的。</p><p><strong>质量与安全</strong></p><p>这个纬度很多公司没有把它单独提出来,但我认为它是非常重要的。很多团队随着时间的推移,通常都会累积越来越多的技术债,最终也无法偿还。在兼顾交付的同时,质量与安全一定是我们长线能看到收益的纬度。因为质量与安全问题带来的隐形成本浪费,是很多团队和公司会忽略的。</p><p><strong>可视化与自动化</strong></p><p>IT工作内容很多是不可见的,因此可视化成了DevOps的重要指标。可视化的好处在于可以构建拉式系统,有助于识别低效环节,并且改善对剩余工作以及当前状态的了解。</p><p>很多团队以为有了流水线就是DevOps了,却不曾想依然有很多人工的工作。DevOps就是要规避人为的风险。因为人是会犯错的,而机器则不会。因此,自动化是非常重要的DevOps成熟度的考量纬度。</p><p><strong>版本与配置管理</strong></p><p>全面的版本控制能帮助团队在开发过程中获得收益,这些版本控制包括但不限于测试、脚本、环境、包、类库、文档、配置等。团队成员可以无风险的删除不需要的文件。</p><p>配置管理也是同样的原理和收益,配置与环境管理使得我们所有的变更都是受控的,系统可以被快速地重置到稳定状态。如果关键成员离开, 知识也不会遗失。</p><p><strong>运维监控与预警</strong></p><p>过去常常由一个独立的运维部门来负责所以线上运维的事情,这种情况在DevOps这里需要发生极大的变化了。运维的事情和开发合并到一起了,由一个团队共同负责了。这也是DevOps所强调的职责共担。同样对于运维的监控和预警也应该是对整个团队可见的。</p><p><strong>持续度量与改进</strong></p><p>DevOps已经越来越多的和效能关联上了。因此也出现了各种关于DevOps或者效能的度量。这些度量不是为了考核KPI,而是帮助团队持续改进的一种手段。DevOps提倡更频繁的直面问题,度量则是一种很好的方式帮助我们发现问题,并持续改进。</p><h4>小结</h4><p>可见,行业里没有一个统一的DevOps成熟度模型,各家都是按照各家的方法论在总结成熟度模型。</p><p>他们没有好坏,他们各有各的优点和侧重点。在不同的场景和不同的公司现状下,选择不同的成熟度模型能帮助我们更好的评估。</p><h2>DevOps成熟度评级</h2><p>关于级别的定义,行业里面普遍有两种,一种是4个级别,一种是5个级别。</p><p>我个人认为5个级别更合理,因为第1级其实就是零,并未尝试DevOps,第5级就是天花板,是以谷歌、微软、亚马逊等公司的领先DevOps团队为代表的天花板。</p><p>因此综合一下,级别定义如下:</p><ol><li><strong>Regressive初始级</strong> - 几乎没有尝试任何DevOps实践</li><li><strong>Basic基础级</strong> - 做了一些DevOps实践,正在起步阶段</li><li><strong>Standard成熟级</strong> - 能成熟运用各种DevOps实践</li><li><strong>Optimized优化级</strong> - 不仅能运用各种DevOps实践,还能根据团队和组织情况进行优化改进</li><li><strong>Leading领先级</strong> - 是行业里面DevOps的先行者、创新者、探索者、领导者</li></ol><p>因此,把这个成熟度模型做成一张可量化的表则如下。</p><p>简单解释一下,表里的描述并不是全部,只是包含了一些关键例子和方向,使用者可以根据自己的情况调整和添加。其次,由于这些纬度很难量化,因此如果只是符合了部分描述,我们也认为没有做到。这样的好处是,我们不会因为评估为成熟级,而放弃了那些成熟级本应该达到而没有达到的描述。</p><p>举个例子,质量与安全纬度中,基础级做到了部分,同时成熟级也做到了部分,我们也认定只达到了基础级。</p><p><img src="/img/bVcRq8N" alt="" title=""></p><p>通过对这张表的打分,最后我们就能得到了一个例如下图的成熟度评估图:</p><p><img src="/img/bVcRq8Q" alt="" title=""></p><h2>总结</h2><p>DevOps成熟度评估模型并不是指导你把DevOps做得更好的方法论,它只是用来评估目前的现状,以指导你未来还有哪些改进空间。</p><p>我做咨询的经历中,发现很多公司会把DevOps当作项目来做,要求团队在N个月内完成DevOps搭建和应用。但DevOps不应该以项目的方式进行,因为项目的方式意味着公司期望在有限的时间以及预算内获得特定的结果,然而DevOps其实是一场没有终点的马拉松比赛。</p><p>如果想知道如何把DevOps做得更好,推荐去看《DevOps精要》那本书,里面讲了DevOps的一些原则和关键实践,掌握这些东西才能持续把DevOps做好。</p><p>下回我也整理一篇文章来讲讲如何把DevOps持续做好。</p>
DevOps效能度量
https://segmentfault.com/a/1190000039820464
2021-04-14T12:28:49+08:00
2021-04-14T12:28:49+08:00
Woody
https://segmentfault.com/u/woodyyan
5
<h2>前言</h2><p>之前做了几个公司的DevOps转型,发现不少公司都比较热衷于如何去度量DevOps效能。(一部分原因是领导们想要以此来考核KPI。)</p><p>度量的方式有很多种,这里我就基于这些年DevOps咨询的经验,总结了一些可度量的指标供大家参考。</p><p>大家可以根据不同的指标组合使用,并结合自己的团队的实际情况,量力而为。有些指标需要自己根据情况开发脚本来统计。</p><p>其实目前市面上的大部分DevOps工具或平台,都或多或少的有了各种数据报表,来帮助团队进行DevOps效能度量。</p><h2>DevOps效能度量指标</h2><p><img src="/img/bVcRfhv" alt="" title=""></p><h4>效能等级</h4><p>效能主要分为4个等级:</p><ol><li>精英效能</li><li>高效能</li><li>中等效能</li><li>低效能</li></ol><p>精英效能则是以谷歌、微软、亚马逊等公司的DevOps先驱团队为代表的团队水准。</p><p>高效能是目前大部分DevOps实践做得好的团队水准。</p><p>中等效能则代表了大部分正在DevOps探索路上的团队水准。</p><p>低效能则是大部分还没开始使用DevOps的团队水准。</p><h4>度量指标</h4><p>度量指标有两种:</p><ul><li>结果指标</li><li>过程指标</li></ul><p>过程指标远多于结果指标,过程指标是帮助我们改进敏捷开发过程的。结果指标多用于做总结汇报。</p><h4><strong>阶段分类</strong></h4><p>DevOps是一个非常长的价值流,那么在这个过程中,我们大致可以把它分为三个阶段来进行效能度量。</p><ol><li>敏捷开发管理</li><li>持续交付</li><li>技术运维</li></ol><p>第一个阶段主要对应了需求和管理部分。</p><p>第二个阶段主要针对整个研发过程。</p><p>第三个阶段主要针对上线后的运维部分。</p><p>所有的指标又可以分为交付效率和质量两类。</p><p>因此把所有指标汇总一下,就如下图一样。</p><p><img src="/img/bVcRfhw" alt="" title=""></p><h2>DevOps效能度量指标详解</h2><ul><li><p><strong>用户故事交付周期</strong></p><pre><code>- 用户故事从创建到上线所需要的时间。
- 计算方式:一个用户故事从创建到上线所需的时间。</code></pre></li><li><p><strong>用户故事吞吐量</strong></p><pre><code>- 一个迭代内能完成的用户故事总数。
- 计算方式:迭代内完成用户故事数。</code></pre></li><li><p><strong>用户故事完成率</strong></p><pre><code>- 一个迭代内,规划的用户故事数与完成的用户故事数的比率。
- 计算方式:实际完成用户故事数/规划用户故事数。</code></pre></li><li><p><strong>团队速率</strong></p><pre><code>- 一个团队每个迭代能完成故事点的数量。
- 计算方式:每个迭代完成故事点数。</code></pre></li><li><p><strong>故事点完成率</strong></p><pre><code>- 一个迭代内,规划的故事点数与完成的故事点数的比率。
- 计算方式:实际完成的故事点/规划的故事点。</code></pre></li><li><p><strong>需求停留时长</strong></p><pre><code>- 一个需求从创建到分析完成后,并进入开发所花的时间。
- 计算方式:需求挪动到“开发中”列的时间点 - 需求出现在“待处理”列的时间点。
- 备注:需求分析完成后在等待开发中也算等待成本。这里其实度量的就是Processing time。</code></pre></li><li><p><strong>研发停留时长</strong></p><pre><code>- 一个需求开发完成所需要的时间。
- 计算方式:需求挪动到“测试中”列的时间点 - 需求出现在“开发中”列的时间点。
- 备注:需求开发完成后在等待测试中也算等待成本。这里其实度量的就是Processing time。</code></pre></li><li><p><strong>测试停留时长</strong></p><pre><code>- 一个需求测试完成所需要的时间。
- 计算方式:需求挪动到“待上线”列的时间点 - 需求出现在“测试中”列的时间点。
- 备注:这里其实度量的就是Processing time。</code></pre></li><li><p><strong>部署频率</strong></p><pre><code>- 代码部署到服务器的频率,不论是测试环境还是生产环境。
- 计算方式:间隔多久部署一次</code></pre></li><li><p><strong>发布频率</strong></p><pre><code>- 代码发布到生产环境的频率。
- 计算方式:间隔多久发布一次。
- 备注:发布频率是小于部署频率的。</code></pre></li><li><p><strong>发布时长</strong></p><pre><code>- 一次发布上线的过程所需的时间。
- 计算方式:一次发布所需时间。</code></pre></li><li><p><strong>发布失败率</strong></p><pre><code>- 发布上线有可能失败,此指标用于统计失败率。
- 计算方式:发布失败次数/发布总次数</code></pre></li><li><p><strong>变更前置时间</strong></p><pre><code>- 从代码提交到代码正式运行在生产环境所需要的时间。
- 计算方式:代码上线时间 - 代码提交时间。</code></pre></li><li><p><strong>构建频率</strong></p><pre><code>- 构建发生的频率
- 计算方式:一个迭代内构建的次数</code></pre></li><li><p><strong>构建失败率</strong></p><pre><code>- CI在构建的时候会因为各种原因失败
- 计算方式:迭代内构建失败次数/迭代内构建总次数</code></pre></li><li><p><strong>CI修复时长</strong></p><pre><code>- CI红了之后需要花时间去修复,此指标统计修复CI所需的时间。
- 计算方式:CI重新变绿的时间 - CI变红的时间</code></pre></li><li><p><strong>代码坏味道数</strong></p><pre><code>- 很多代码扫描工具都能统计代码坏味道,比如Sonar。
- 计算方式:单次构建扫描的坏味道数,取多次的平均数。</code></pre></li><li><p><strong>代码重复率</strong></p><pre><code>- 重复代码在代码库中的占比。Sonar等工具能统计结果。
- 计算方式:重复代码数量 / 总代码数。</code></pre></li><li><p><strong>代码扫描bug数</strong></p><pre><code>- Sonar等工具能扫描出代码中存在的bug数。
- 计算方式:迭代内代码扫描bug出现的次数。</code></pre></li><li><p><strong>代码扫描漏洞数</strong></p><pre><code>- Sonar等工具能扫描出代码中存在的漏洞数。
- 计算方式:迭代内代码扫描漏洞出现的次数。</code></pre></li><li><p><strong>代码提交频率</strong></p><pre><code>- 统计每日代码提交的次数。
- 计算方式:每日代码commit或push次数。</code></pre></li><li><p><strong>代码合并次数</strong></p><pre><code>- 统计周期时间内代码合并的次数。
- 计算方式:迭代内代码merge request次数。</code></pre></li><li><p><strong>代码评审通过率</strong></p><pre><code>- 每次代码评审都有可能因为代码质量原因不通过。
- 计算方式:迭代内代码评审通过次数/迭代内代码评审总次数</code></pre></li><li><p><strong>自动化测试率</strong></p><pre><code>- 测试工作有多少是自动化完成的,而不依赖于人工测试。
- 计算方式:(自动执行测试用例数 - 人工执行测试用例数)/ 总测试用例数
- 备注:测试分为很多类型:单元测试、集成测试、验收测试、性能测试、安全测试等,可以分别度量。</code></pre></li><li><p><strong>自动化测试失败率</strong></p><pre><code>- CI跑的测试是会因为各种原因失败的,这个指标用于统计失败的频率。
- 计算方式:CI测试失败次数/CI测试执行总次数</code></pre></li><li><p><strong>测试覆盖率</strong></p><pre><code>- 测试所覆盖的代码的比率
- 计算方式:大部分测试工具都能自动统计测试覆盖率,比如Jacoco</code></pre></li><li><p><strong>缺陷修复时长</strong></p><pre><code>- 一个缺陷修复所需的时间。
- 计算方式:一个缺陷修复所需的时间。</code></pre></li><li><p><strong>缺陷密度</strong></p><pre><code>- 统计一个迭代内缺陷出现的密度。
- 计算方式:迭代内缺陷个数/迭代内用户故事数</code></pre></li><li><p><strong>服务恢复时间</strong></p><pre><code>- 一次服务故障恢复所需的平均时间。
- 计算方式:服务恢复时间 - 服务故障发生时间</code></pre></li><li><p><strong>生产问题个数</strong></p><pre><code>- 统计周期时间内生产问题的个数,多次统计后以计算平均数。
- 计算方式:用户故事上线后2个迭代内生产问题的个数。</code></pre></li><li><p><strong>生产问题密度</strong></p><pre><code>- 统计周期时间内生产问题产生的密度。
- 计算方式:用户故事上线后2个迭代内发生的问题个数/2个迭代的用户故事数
</code></pre></li></ul><p>每个指标对于具体的等级的数值请参考下表。</p><p><img src="/img/bVcRfhz" alt="" title=""></p><h2>总结</h2><p>DevOps是敏捷开发的一种演进,通过结合人、流程与工具,持续改进研发效能。</p><p>因此度量只是一种可视化手段,真正能提高效能的还是要从文化、组织、技术等方面去建设DevOps。不断消除浪费,提高生产效率。</p><p>对于此DevOps效能度量有任何疑问欢迎找我讨论。我也会在后续的咨询工作中不断去总结和改善这个效能度量指标。</p>
谈一谈线上事故的故事
https://segmentfault.com/a/1190000039805146
2021-04-11T13:44:53+08:00
2021-04-11T13:44:53+08:00
Woody
https://segmentfault.com/u/woodyyan
1
<h2>背景</h2><p><img src="/img/bVcRbia" alt="kelvin-ang-QvU0LNnr26U-unsplash.jpg" title="kelvin-ang-QvU0LNnr26U-unsplash.jpg"></p><p>做过不少公司的咨询,发现有些公司对于线上事故没有规范化的认知,没有预防措施,发生之后也没有一个规范的流程去响应。甚至有的公司发生线上事故之后,没有监控没有预警,只有开发或运维知道,然后他们就悄悄的把线上事故修复了,神不知鬼不觉。</p><p>那么接下来我就从下面这几个方面来谈一谈线上事故的故事。</p><h2>线上事故预防</h2><p>线上事故预防分为3个方面</p><ul><li>预发环境</li><li>上线checklist</li><li>日志与运维监控</li></ul><h4>预发环境</h4><p>预发环境也叫UAT环境,或者预生产环境等。</p><p>非生产环境中应该有一个预发环境,此环境理论上应该和生产环境一模一样,包括一样的配置来保证性能一致,一样的数据来保证行为一致。</p><p>这样才能最大限度的模拟生产环境,并提前发现问题解决问题,起到沙盘演练的作用。</p><p>预发环境的位置如下图:</p><p><img src="/img/bVcRbid" alt="Screen_Shot_2021-03-23_at_17.31.44.png" title="Screen_Shot_2021-03-23_at_17.31.44.png"></p><p>预发环境搭建好了之后,应该定期的同步生产环境的数据到预发环境做测试。</p><p>注意:同步数据时得有一定的脱敏策略,不然会有安全风险。</p><p><strong>影子流量</strong></p><p>由于预发环境毕竟不是生产环境,所有的请求都是由测试人员模拟的,并不是真实的情况。</p><p>而影子流量(shadow traffic)就是将发给生产环境的请求复制一份转发到类生产环境上去,以此来达到压力测试和正确性测试的目的。</p><p>影子流量的实现有多种方式,常见的比如在统一的入口处API Gateway等地方复制一份流量转发到预发环境,由此来监测预发环境有没有异常情况发生。</p><p>影子流量的过程如下图所示。</p><p><img src="/img/bVcRbit" alt="Screen_Shot_2021-04-09_at_15.34.16.png" title="Screen_Shot_2021-04-09_at_15.34.16.png"></p><p>由于影子流量来自真实的生产环境,过程中一定要注意安全防范,以及流量转发给生产环境性能带来的性能影响。</p><p><strong>影子库</strong></p><p>同理影子流量,理论上预发环境应该有一份和生产环境一模一样的数据库,才能更真实的模拟生产环境的情况。影子库就是把生产环境的数据库复制了一份,有相同的数据量和相同的数据库配置。</p><p>当然,理论上,影子库的数据应该是经过脱敏处理之后的。</p><h4>上线checklist</h4><p><img src="/img/bVcRbiu" alt="kelly-sikkema--1_RZL8BGBM-unsplash.jpg" title="kelly-sikkema--1_RZL8BGBM-unsplash.jpg"></p><p>上线前应该有一份不断累计不断更新的checklist,用于上线前的检查,每项检查都通过之后,方能上线。</p><p>有任何一项检查不通过,则不能上线。直到问题解决之后,才能上线。另外,应该避免在业务高峰期进行上线操作。</p><p>下面是一些常见的checklist项,不同的项目会根据需要有不同的checklist:</p><ul><li>测试报告是否已通过</li><li>业务验收报告是否已通过</li><li>是否有其他版本正在上线</li><li>是否有线上问题未解决</li><li>是否有回滚方案</li></ul><p>虽然它是checklist,但很多公司在实践的时候都把它做成了上线报告。</p><p>更推荐的做法是把checklist做得越轻量级越好,就像checklist,轻量级的检查就能上线。</p><p>不然就会让上线变得越来越困难、越来越花时间,违背了持续交付的理念。</p><h4>日志与运维监控</h4><p>如果有非常完备的日志系统,比如常见的ELK,splunk等,则可以在预发环境的影子流量中和生产环境中,监测到异常日志,提前发现问题,提前解决问题。</p><p>日志监控是一个挺大的话题,这里就不展开讲了。</p><p>但日志是我们定位问题和追溯问题中不可或缺的一环。</p><h2>线上事故修复</h2><h4>线上报警</h4><p><img src="/img/bVcRbiv" alt="jessica-tan-wL7aOdzTtcY-unsplash.jpg" title="jessica-tan-wL7aOdzTtcY-unsplash.jpg"></p><p>线上环境应该有监控预警,一旦发生任何事故,都应该及时报警。</p><p>这些事故可能发生的情况包括但不限于:</p><ul><li>服务不可用</li><li>数据库不可用</li><li>服务器不可用</li><li>高级别日志警告</li><li>流量异常</li></ul><p>发生了事故,应该第一时间自动通知开发团队,并抄送相关负责人或领导。收到邮件或通知的开发团队则应该第一时间修复该线上问题。</p><p>邮件中应该包含事故的现象描述,原因,日志等信息,以方便开发团队分析和修复。</p><p>最重要的是,上述过程都应该是全自动化的,才能保证开发人员或者运维人员第一时间响应。</p><p>通常一个团队中会建立某种机制来保证发生线上事故的时候有人能及时响应事故。</p><p>我见过有的公司是运维人员24小时待命的。</p><p>也见过做的比较好的公司是,团队成员轮流做那个站岗的人,系统通知自动绑定站岗人的电话和邮件,发生线上事故之后,第一时间自动逐级通知站岗人,负责人,领导等。由站岗人作为线上事故的owner,他不一定能修,但他要保证问题被修复。如果事故发生在工作时间以外,则根据响应的时间来调休。</p><h4>回滚机制</h4><p>优秀的团队都是使用CI/CD的pipeline来进行上线部署,一旦失败了,pipeline都是支持回滚到上一个版本的。</p><p>如果是使用的Kubernetes,则只需要回滚到上一个版本的镜像则可以了。</p><p>这就要求,我们每次构建的包或者镜像都应该带上版本号,才方便我们追踪每个版本的状态,以及根据版本来进行回滚操作。</p><h4>灾备恢复</h4><p><img src="/img/bVcRbiw" alt="science-in-hd-3kgzvab8SMg-unsplash.jpg" title="science-in-hd-3kgzvab8SMg-unsplash.jpg"></p><p>线上事故如果是灾难性的,则应该启动灾备恢复程序。 我了解的大部分公司其实都没有灾备恢复的相关流程。理论上,一个意识比较好的公司,通常都会有严格的灾备恢复流程。</p><p>灾备恢复可以是一个操作手册,指导员工如何一步一步的从0恢复服务。通常银行业是一定有这样的灾备恢复的。</p><p>同时,每次发生了大的基础设施架构变更的时候,比如服务器配置变化,数据库变化等,都需要及时更新灾备恢复手册。</p><h2>线上事故复盘</h2><p><img src="/img/bVcRbiD" alt="kelly-sikkema-ElF7K4IWcGQ-unsplash.jpg" title="kelly-sikkema-ElF7K4IWcGQ-unsplash.jpg"></p><p>线上事故修复之后,需要对此事故做一次复盘,目的是为了避免下次再发生,以及发生了之后能更快的应对。</p><p>这个复盘有很多种形式。其中一种形式叫无过错验尸报告。</p><p>具体的介绍可以看我的另外一篇文章:<br><a href="https://segmentfault.com/a/1190000039742180">无过错验尸报告 - Blameless Postmortem</a></p><h2>总结</h2><p>这里简单列举了一些关于线上事故我能想到的几个点,如果大家对于这个话题还有其他感兴趣的部分,欢迎给我留言,我们下回接着探讨。</p>
无过错验尸报告 - Blameless Postmortem
https://segmentfault.com/a/1190000039742180
2021-03-30T15:38:49+08:00
2021-03-30T15:38:49+08:00
Woody
https://segmentfault.com/u/woodyyan
1
<h2>前言</h2><p>在咨询的经历中,发现有些软件项目经常出现线上事故,出现了线上事故之后,第一时间会去修复这个问题,第二时间,则是问责。</p><p>这是一个很有意思的现象,通常在一些传统行业的团队或者政府背景的团队中,发生了线上事故,他们会启动问责程序,找到事故的负责人,并对他做出相应的处罚。</p><p>作为程序员,大家都知道,代码的世界不出错是不可能的。问责在很大程度上会导致团队成员不敢写代码,不敢上线,不敢触碰线上环境的一切东西,最终导致团队研发效率下降。</p><p><strong>那正确的做法应该是什么呢?</strong></p><p>这里就给大家介绍一下Blameless Postmortem,中文意思就是<strong>无过错验尸报告</strong>。</p><h2>什么是无过错验尸报告?</h2><p>无过错验尸报告是对线上事故的书面记录,用来描述:</p><ul><li>这一线上事故的影响。</li><li>减轻或解决事故所采取的行动。</li><li>事故的根本原因。</li><li>为防止该事故再次发生而采取的后续行动。</li></ul><p>无过错验尸报告这个名字是英文直译过来的,如果觉得这个名字过于血腥,可以叫它无过错反思报告,或者无过错事故报告,或者无过错事后分析报告。但更多的人都习惯亲切的叫它验尸报告。</p><p>之所以强调无过错,是因为这样的话人们就不会在写报告的时候由于害怕被问责,从而互相埋怨或者隐藏自己的过错。</p><h2>为什么需要无过错验尸报告?</h2><p>验尸报告的目标是了解所有导致事故的根本原因,记录事故的经过以供未来参考,并制定有效的预防措施以减少事故再次发生的可能性。</p><p>为了使验尸报告能够有效地减少重复事故,总结过程必须激励团队识别根本原因并修复它们。</p><p>同时,关注这个过程并确保它是有效的则需要组织中各级的承诺。比如不能出现对团队某个人的问责。</p><h2>什么时候需要无过错尸检报告?</h2><p>线上事故都会有严重程度或者影响程度分级,因此,通常我们只会对级别较高的事故写尸检报告。</p><p>我们通常会在下面两个时间点开始写尸检报告:</p><ul><li>修复事故期间</li><li>修复事故之后</li></ul><h2>谁完成验尸报告?</h2><p>事故产生的服务所属的交付团队共同负责完成验尸报告。</p><p>但需要选择一名owner来主要负责编写报告,并且这个owner需要保证下面两件事情的发生:</p><ol><li>分配不同的人去完成各类的事故调研工作,最后把结果汇总给这个owner。</li><li>保证报告中的改进action按照紧急程度安排到后面相应的迭代中。</li></ol><h2>如何跟踪报告中的action?</h2><p>这个问题其实是紧接上面的第二条。</p><p>报告中的action通常分为两类:</p><ol><li>根本原因改进</li><li>非根本原因改进</li></ol><p>对于报告中的每个action:</p><ul><li>应该在对应的团队的backlog中建卡,并根据优先级安排进相应的迭代。</li><li>Owner要负责跟踪卡的完成情况。并记录到报告中。</li></ul><h2>验尸报告会议</h2><p>验尸报告相关的会议有两种。</p><ol><li>一种是在编写报告前,用于讨论事故的根因。</li><li>一种是在报告完成后,用于向团队分享报告内容,学习成长。</li></ol><p>不管是哪种会议,都要记住,这个会议不是批斗会,不能在会议中指责任何人。</p><p>这里的指导原则和retro类似。</p><p>实践过程中,我发现大部分团队只会开第一个会议,在编写报告的过程中,大家基本上都学习了,并了解了报告中的根因。所以大部分团队都不会开第二个会议。</p><p>但是不少公司会开另外一种会议,就是报告写完了之后给领导的汇报会议,此会议根据不同公司的政策不同,可有可无。</p><h2>报告模版</h2><p>下图就是一个完整的报告模版。</p><p>报告可以是表格的形式,也可以是文档的形式。有了模版,写报告的人就可以照着模版往里面填内容了。</p><p><img src="/img/bVcQUUI" alt="验尸报告" title="验尸报告"></p><p>关于如何识别根因,Atlassian提供了一种叫5 Whys的分析方法,具体怎么做可以参考这里:</p><p><a href="https://link.segmentfault.com/?enc=1XKVBF3yX9jxJj6ya623KA%3D%3D.Uq9w5uPB1YJfDSxgv4TdUJ8cAe4GphwDTQNlhCTQsU3EZs78nou1NtGC4WfpAuwN3HojgnaPG0TsTNihhyCrVWEfQKVruaPzKbP4i9JpGQo%3D" rel="nofollow">https://www.atlassian.com/tea...</a></p><h2>总结</h2><p>验尸报告是为了在软件开发过程中以及项目交付过程中能持续改进,有记录能存档,并且成为知识沉淀的一部分。</p><p>所以它的形式可以根据团队的实际情况来。我见过有团队用表格来写的,像上面模版那样,也有直接写卡上的,也有直接开会画在白板上然后拍下来的。</p><p>对于无过错验尸报告,大家在实践过程中有任何疑问,欢迎来找我讨论。</p><hr><p>参考资料</p><p><a href="https://link.segmentfault.com/?enc=DuEFU2xhgZca3cJ7Vpk3iQ%3D%3D.Ppl3ftHTue6EPzftpwHyKfJCFq3w%2BDtG1wUrwzts6QPMeRUZkhC4fzoAYD%2BkMuKBl67dak9uNd5mLjJHmuDOE9DADG8gzMjKVaK3lPGHv%2F6f6rzpw%2BdhSwfOi9hEBNXG" rel="nofollow">https://www.atlassian.com/inc...</a><br><a href="https://link.segmentfault.com/?enc=01SS5oHZeOSPt22htuRoug%3D%3D.sldqLRMIOOBW4b%2FYwPA%2FRWnC0rpJlXnI9ICjz3bVYECoqOfk8%2FCexpO04ouZElOfO1z01OiVF9iWjSBSNpPVABTVzbbYgHmRnNJ4TEU02Gc%3D" rel="nofollow">https://www.atlassian.com/inc...</a></p>
敏捷迭代日历
https://segmentfault.com/a/1190000039733940
2021-03-29T14:00:15+08:00
2021-03-29T14:00:15+08:00
Woody
https://segmentfault.com/u/woodyyan
1
<h2>前言</h2><p>我咨询过的项目中,发现很多团队想用敏捷,但是不知道如何做。那我们就从最基础的一个迭代日历开始吧。</p><p>经过多个项目的迭代,总结了一个适合于大部分团队的敏捷迭代日历。</p><p>不管是在敏捷的理论里面,还是scrum的理论里面,下面总结的活动都多少有一些差异。但敏捷不是说一定要严格按照标准的活动来进行,而是拥抱变化持续改进,找到最适合于自己团队的最佳实践,才是敏捷的核心。</p><h2>敏捷迭代日历</h2><p><img src="/img/bVcQSIA" alt="Screen_Shot_2021-03-24_at_15.35.41.png" title="Screen_Shot_2021-03-24_at_15.35.41.png"></p><p>图中所展示的是一个迭代内发生的所有活动。</p><p>通常一个迭代是2周。横向是两周的时间线,纵向是一天的时间线。</p><p>紫色的是当前迭代会进行的活动,蓝色的是需要在本迭代为下个迭代做准备的活动,橙色是总结上一个迭代的活动。</p><p>接下来我们就每个活动展开来讲解。</p><h3>站会Standup</h3><p>站会通常发生在一天的开始。之所以叫站会,是因为大家站着开,才能让会议尽快结束。站会长如图的样子。<br><img src="/img/bVcQSIF" alt="Untitled.png" title="Untitled.png"><img src="/img/bVcQSII" alt="2.png" title="2.png"></p><p><strong>站会的目的是什么?</strong></p><ul><li>促进和改善团队协作</li><li><p>让团队达成一致</p><ul><li>理解共同的目标</li><li>互通不同成员的工作内容</li></ul></li><li>消除障碍</li></ul><p><strong>站会如何进行呢?</strong></p><p>开站会通常有两种模式。</p><p>一种是按人头更新,每个人轮流说下面三个问题:</p><ol><li>昨天做了什么?</li><li>今天准备完成什么?</li><li>有没有任何问题或阻碍?</li></ol><p>第二种模式是按卡更新,在看板上从右至左的更新每一张卡的状态,及时更新卡的状态,同时也要及时暴露该卡有没有任何问题或阻碍。</p><p><img src="/img/bVcQSJP" alt="Screen_Shot_2021-03-22_at_11.47.33.png" title="Screen_Shot_2021-03-22_at_11.47.33.png"></p><p><strong>谁参与站会呢?</strong></p><p>整个团队的人都应该参与。也是那些坐在一张桌子上工作的人。</p><p><strong>站会需要多长时间呢?</strong></p><p>通常不应该超过15分钟,这也是为什么要站着开的原因,这样才能让大家保持专注,尽快结束会议。</p><p><strong>做好站会还有哪些tips呢?</strong></p><ul><li>站会要固定时间,不能因为某人缺席而推迟。不然会打乱团队的节奏。</li><li>不要在站会上引入新东西,只聚焦在当前迭代的范围中。新东西应该在其他时间或其他会议中提出来。</li><li>为了让站会尽快结束,可以准备一个小物件作为信物,拿到信物的人才能发言。大家轮流传递信物进行发言,这样可以避免大家你一言我一句的发散导致会议时间过长。</li><li>每次站会都应该有一个facilitator。作为站会的推动者负责保证站会高效有序的进行,并更新相关的状态。</li></ul><h3>迭代计划会议Iteration Planing Meeting</h3><p>迭代计划会议的举行就标志着上个迭代结束了,下个迭代开始了。因此,我们会在这个会议上在看板中关闭上个迭代,然后开启下个迭代。</p><p>迭代计划会议大概长下图的样子,大家打开迭代看板和backlog,然后讨论下一个迭代要做什么。并设置好下一个迭代的看板。</p><p><img src="/img/bVcQSKx" alt="leon-vbxyFxlgpjM-unsplash.jpg" title="leon-vbxyFxlgpjM-unsplash.jpg"></p><p><strong>目的</strong></p><p>这个会议的目的就是让团队决定接下来这个迭代中,我们要做哪些工作(哪些故事卡)。</p><p><strong>如何进行?</strong></p><p>通常会考虑团队工作载量,比如一个团队每个迭代能完成30个点,那么就会计划30个点到下一个迭代中,同时会选择优先级高的卡优先完成。</p><p>然后团队一起决定下个迭代做哪些卡,这些卡是否都已经是ready的,如果不ready的卡不应该放到下个迭代中。</p><p><strong>谁参与呢?</strong></p><p>理论上这个会议需要Product Owner参加,他可以回答大家对于这个迭代的工作的问题。实践中发现国内大部分公司没有Product Owner,而类似的角色是提出需求的业务方。因此实践中只需要让团队所有成员参与就可以了。</p><p>而对于需求的澄清,会议前应该让产品经理或BA提前完成和业务方的需求澄清和优先级排序。</p><p><strong>会议需要多少时间?</strong></p><p>通常在<strong>一个小时</strong>以内。为了保证会议能按时结束,有些工作会放到会前或者其他会去做,比如估点、卡的需求澄清等。</p><p>迭代的开始不一定是周一。可以是团队觉得合适的任何一天,比如周三。</p><h3>当前迭代的工作</h3><p>迭代开始后,开发和测试人员会工作在当前迭代的卡片中。</p><p>如果遇到不清楚的细节,就可以找产品经理或BA澄清。需求澄清应该是一个随时随地发生的动作。</p><h3>代码评审Code review/技术分享</h3><p>有的团队会把代码评审和技术分享分开来做。大部分团队没有太多技术分享,因此可以放到一起。</p><p>代码评审理论上每天都会发生,而技术分享则看情况。做得好的团队,每周都会有技术分享。</p><p><img src="/img/bVcQSK6" alt="heylagostechie-YgOCJz9uGMk-unsplash.jpg" title="heylagostechie-YgOCJz9uGMk-unsplash.jpg"></p><p><strong>代码评审的两种模式</strong></p><ol><li>团队成员各自去查看Merge request并留下评论。</li><li>团队成员集中时间一起看Merge request并留下评论。</li></ol><p>第一种模式是为了解决团队很难有统一的时间来一起看merge request,则把代码评审的集体时间分散到团队成员各自的零散时间中去做。</p><p>第二种模式的好处是,团队可以一起分享业务和分享技术,通常在代码量比较小的时候,快速做完代码评审之后,团队可以商量由某个人分享一些大家不知道的业务或者技术,分享的内容一定要小巧,比如10分钟就分享完,如果是大分享则应该单独计划一个时间来做。</p><p>第二种模式的review时间应该控制在30分钟到60分钟以内。时间宽裕的团队,可以做1小时,加上技术分享等。时间不宽裕的团队,则建议在30分钟内结束。因此为了让代码评审快点结束,可以安排一个facilitator来防止大家发散,推进代码评审的进度。</p><p>第二种模式的代码评审通常会在每天下午的5点到6点之间进行,这样的好处在于可以回顾大家一天所写的代码。</p><p><strong>代码评审的tips</strong></p><ul><li>每个Merge Request都应该有2个及以上的人通过了,才能合并。</li><li>小步提交。提交越小步,代码review起来越容易。</li><li>发现任何有问题的代码或者疑惑的地方,都应该直接问或在代码旁边留下评论。</li><li>代码评审不是批斗会,而是互相了解业务与技术,互相学习的机会。</li><li>代码评审不是只有leader才有权利去评审,任何团队成员都有权利去做。</li><li>代码评审时,应该先从与代码相关的业务说起,让其他人对于代码的业务背景有了解。</li><li>代码评审是互相学习的好机会,是拉齐团队编码能力的好机会。</li></ul><p><strong>代码评审的好处</strong></p><ol><li>知识共享</li><li>质量保证</li><li>技术氛围建设</li><li>快速反馈</li><li>编码能力提升</li></ol><h3>CI/CD</h3><p>CI是continuous integration,也就是持续集成。</p><p>CD是continuous delivery,也就是持续交付。也会说是continuous deployment,也就是持续部署。</p><p>CI/CD是我们在开发过程中快速迭代的基本质量保障,它能让我们快速获取反馈。是必不可少的部分。</p><p>这个话题也挺大的,就不在这里展开了。</p><p>下次单独找个时间来分享这个话题。</p><h3>迭代估算会</h3><p>有的团队会把这个会议的内容放到迭代计划会IPM中。但在实践中我们发现,下一个迭代的准备工作可以单独划分一个迭代估算会议来做。</p><p>那么这个迭代估算会主要做以下两件事情:</p><ol><li>澄清故事卡的需求,确保团队内部对它的理解一致。如果不一致,则需要产品经理或BA去找业务方澄清。</li><li><p>大家一起为故事卡估点。</p><ol><li>故事点(Story Point)通常代表这张卡的复杂度或者工作量,有的团队也用它来代表价值。</li><li>点数通常有两种方式,一种是使用斐波拉契数列,一种是使用T-shirt size。第一种比较常见。</li><li>由于故事卡估点也是一个比较大的话题,这里就不展开叙述了。</li></ol></li></ol><h3>下个迭代的工作</h3><p>下个迭代的工作主要分为下面三个方面。</p><ol><li>产品经理或者BA会在当前迭代去准备下个迭代的故事卡,完善那些待分析的卡,补全需求细节,画出原型图等。不清楚的需求就找业务方澄清。</li><li>设计师对需求已经清楚的故事卡设计高保真的UI图。</li><li>可能会有一些新技术的调研工作,可以安排个别开发人员对下个迭代可能用到的技术做调研。</li></ol><h3>Retro回顾会</h3><p>回顾会的英文是Retrospectives,俗称retro。</p><p>每个迭代结束之后都会进行Retro回顾会,目的是为了回顾过去展望未来。总结上个迭代中做得好的,可以继续保持,然后看看哪些地方可以改进。</p><p><strong>安全检查</strong></p><p>在有必要的团队,回顾会可以有一个安全检查,安全检查就是让大家投票觉得现在这个回顾会可以安全进行吗,如果大家的投票是NO,则把参会人员中职位最高的人请出去,再进行投票,直到大家都认为安全后,再进行回顾会。</p><p>实践中,大部分团队都不需要安全检查。如果需要,可以反思一下,为什么大家的团队安全感很低。</p><p><strong>最高指导原则</strong></p><p>回顾会不是批斗会,因此回顾会有一个最高指导原则,目的是为了创造一个安全的环境,在这个环境中,团队成员可以自由检查他们的流程和工具,而且不必担心别人的指责。</p><p>最高指导原则就是:</p><blockquote>无论我们发现了什么,考虑到当时的已知情况、个人的技术水平和能力、可用的资源,以及手上的状况,我们理解并坚信:每个人对自己的工作都已全力以赴。</blockquote><p>最高指导原则的好处在于为了最大化知识的产出而将思考的重点暂时从“人”移开。</p><p>回顾会经过这么多年的发展,已经有非常成熟丰富的模版来做了。下图就是一些常见的retro模版。</p><p><img src="/img/bVcQSLp" alt="screencapture-metroretro-io-templates-2021-03-24-15_19_27.png" title="screencapture-metroretro-io-templates-2021-03-24-15_19_27.png"></p><p>模版来自:<a href="https://link.segmentfault.com/?enc=%2BnEe%2Fu4%2FY3CJHrbUQJ82xg%3D%3D.ooMPFEthh9diGaqucMWcTQ2nq0%2Bdiv5zc3zKSKUxm40%3D" rel="nofollow">https://metroretro.io/templates</a></p><p><strong>回顾会的几个小tips</strong></p><ul><li>大家讨论出来的改进项可能会很多。大家可以投票选出优先级最高的前几个action来执行。</li><li>Action必须有owner,没有owner的action就意味着没有人会去做。</li><li>每次回顾会开始的时候,应该先回顾一下上次回顾的action是否都完成了。</li><li>回顾会不一定是迭代结束就马上做,这样会让当天同时有迭代计划会和回顾会,占用团队太多时间。实践中发现,如果IPM在周一,则retro可以在周三比较合适。</li></ul><h3>Showcase</h3><p>每个迭代结束后,需要向PO或者业务方做showcase,展示工作成果,同时获取业务方反馈或批复,以持续改进。</p><p>Showcase通常需要团队中的开发、测试、产品经理/BA出席,同时需要业务方或者PO出席。</p><p>实践过程中,很多团队会通过showcase会来获得业务方的批复(Sign off),以取得上线许可。</p><h3>上线</h3><p>上线的时间不是固定的,每个团队会根据每个团队自己的实际情况来定。</p><p>咨询这么多年,我见过随时持续上线的,见过半夜三更上线的,也见过每月固定时间上线的。</p><p>通常敏捷是强调小步快跑的,因此,每个迭代做完的工作就应该及时上线。</p><p>这样的好处在于上线的内容越少,出现问题的影响越小,回滚成本越小。同时也能更快的获取用户反馈。</p><p>另外,上线时间安排在周一的原因在于,如果是周五上线,出了问题,大家周末都不好过了。</p><h2>总结</h2><p><img src="/img/bVcQSLz" alt="eden-constantino-OXmym9cuaEY-unsplash.jpg" title="eden-constantino-OXmym9cuaEY-unsplash.jpg"></p><p>我就不赘述瀑布模式和敏捷的区别了。就简单说说我们把需求或者工作划分成每个迭代来完成,有以下几个好处。</p><ol><li>我们可以通过燃尽图等相关统计报表,分析团队的研发效率。</li><li>通过多个迭代的开发,我们就可以精确度量团队工作载量和速率,更好的利用团队资源。</li><li>按迭代回顾和改进,也符合敏捷小步快跑的理念。</li><li>工作范围和内容清晰明确。</li><li>可视化未来的工作。</li><li>更早的识别风险。</li><li>降低了因为变化带来的风险,让团队更快的响应变化。</li></ol><p>最后,本文只是从敏捷迭代日历的角度谈了谈敏捷相关的实践,如果对敏捷其他话题感兴趣,欢迎给我留言,我们再继续讨论。</p>
敏捷改造(下):真实案例敏捷改造
https://segmentfault.com/a/1190000039716732
2021-03-26T10:27:43+08:00
2021-03-26T10:27:43+08:00
Woody
https://segmentfault.com/u/woodyyan
1
<p>上篇分析了我做过的一个真实的项目的研发过程中的种种问题,那么这篇就来讲解一下我们如何针对这些问题做敏捷改造。</p><h2>怎样用敏捷做改进</h2><p>小瀑布模式的缺点在于它的沟通成本、等待成本、返工成本依然很高,因此我们可以考虑从这3个角度出发去做改进。</p><p>我画了一个图来展示小瀑布模式和敏捷开发的详细对比。</p><p><img src="/img/bVcQOh9" alt="Screen_Shot_2021-03-15_at_11.23.15.png" title="Screen_Shot_2021-03-15_at_11.23.15.png"></p><p>图中紫色管道是小瀑布模式,蓝色管道是敏捷开发,两个管道是相同的团队管道容量,换句话说,两种模式的工作载量是相等的。</p><p>横向是时间线,从左到右,按需求提出开始直到此需求上线,即为此需求的生命周期。</p><p>首先,先说一下为什么这3个成本很高。</p><p><strong>沟通成本</strong></p><p>产品经理通常需要1-2小时来与业务方进行需求沟通,沟通完了之后,产品经理会有一个初步方案,然后会与团队内部的技术人员,测试人员等,一起评审这个需求,如果这中间有任何疑问,产品经理就需要不停的反复在业务方与技术人员中间进行沟通。因此沟通成本是比较高的。</p><p>产品经理通常也需要1-2小时来与技术人员一起做技术评审,时间比较长,很多问题细节需要来回反复确认,沟通成本很高。</p><p>总结一下就是,由于需求粒度比较大,每个环节都比较重,有大量的细节需要讨论和确认,因此带来了较高的沟通成本。</p><p><img src="/img/bVcQOii" alt="Screen_Shot_2021-03-11_at_18.43.46.png" title="Screen_Shot_2021-03-11_at_18.43.46.png"></p><p><strong>等待成本</strong></p><p>开发只有等产品文档完全设计好了,才能开始开发,由于需求粒度过大,此设计过程相对过长,因此开发的等待时间也长,没有充分利用开发资源。</p><p>同理,测试也是,只有等开发提测了才能做测试。但测试在此之前,会先写测试用例,因此测试资源浪费还算较小。</p><p>但另外一个问题是,测试一次性要写非常多的用例测试,一次性测那么多用例,测试的完整性完全依赖于测试人员的耐心。</p><p><img src="/img/bVcQOij" alt="Screen_Shot_2021-03-11_at_18.44.26.png" title="Screen_Shot_2021-03-11_at_18.44.26.png"></p><p><strong>返工成本</strong></p><p>测试如果发现问题,会让开发返工,由于需求粒度比较大,经常出现测试发现多个问题的情况,那么就会来回的多次返工与测试。</p><p>甚至有时候返工会直接返到业务方去重新确认需求的细节。</p><p><img src="/img/bVcQOil" alt="Screen_Shot_2021-03-11_at_18.47.35.png" title="Screen_Shot_2021-03-11_at_18.47.35.png"></p><hr><h2>敏捷研发过程改进</h2><p>那么敏捷是如何改进这个过程的呢?</p><p>首先敏捷是提倡小步快跑,拥抱变化。目前由于需求粒度比较大,无法小步快跑,同时开发到中间的时候的,需求突然变化,应对起来也比较慢。</p><p>那我们就可以根据下图来进行改造。</p><p><img src="/img/bVcQOh9" alt="Screen_Shot_2021-03-15_at_11.23.15.png" title="Screen_Shot_2021-03-15_at_11.23.15.png"></p><p>上图中最大的变化就是把原来的需求A拆解成了3个story。</p><p>敏捷提倡小步快跑,那么管理需求也一样,只有需求足够小,才更利于我们快速理解和分析。而user story(用户故事)则是敏捷里面的一个可以工作的最小单位。</p><blockquote>用户故事在软件开发过程中被作为描述需求的一种表达形式;为了规范用户故事的表达,便于沟通;包含角色、活动、价值三个要素。</blockquote><p>瀑布式的需求管理和敏捷需求管理的区别在于:</p><ul><li>瀑布式的需求分析要求在一开始就获取所有需求,分析所有细节,并且假设我们可以对软件项目有个完美的预测。</li><li>而用户故事则基于我们不能完美预测,不能在一开始就知道所有细节的基础。因为我们对需求的理解是一个逐渐清晰的过程。同时,在项目开始时尝试编写所有的需求忽略了重要的反馈循环。用户故事承认故事的时间维度,随着时间的推移以及功能的增加,会有新的用户故事产生,或者使故事的相关性发生变化。所以要延迟细节,融入业务到整个软件开发过程中,鼓励交流和沟通。</li><li>另外,做了用户故事拆分之后,产品经理或者BA需要补全细节,不停的做需求澄清,和业务方做sign off。</li><li>敏捷需求管理会借助JIRA等工具进行可视化的看板或者scrum管理。而不是基于传统的Excel管理。</li><li>每个故事写好之后,会让业务方做card sign off,比如在卡下面留言ready to go等。如果每张卡做sign off太频繁,可以由产品经理或BA单独找业务方用邮件等的形式针对一个epic统一做sign off。</li><li>敏捷里其实没有一个专门给业务方和产品经理/BA的需求澄清会,因为默认为已经发生在日常工作中了,按理说应该分析一张卡确认一张卡,才能尽可能减少因一张卡片理解不到位引起的大面积返工。</li></ul><p>拆解成小的用户故事之后有如下一些好处:</p><ol><li>原来产品经理和业务方的沟通成本随着需求被拆成小的用户故事而变小了。</li><li>由于用户故事比较小,分析完成的时间就变快了,产品设计的时间也变快了,那么开发开始的时间也就变快了,减少了等待成本。</li><li>由于开发时间更短,第一个用户故事测试时间也就提前了,因此如果出现问题需要返工,那么返工的时间比原来就更早,返工修改的内容也更少,能较快的完成返工并重新测试。整体返工成本就变小了。</li><li>由于各方时间都提前了,那么第一个用户故事上线的时间也提前了,业务方就能更早的看到需求的部分功能,就能更早的反馈问题。</li><li>由于每个环节的沟通成本,等待成本,返工成本均减少了,因此整个需求的交付时间也就提前了。</li></ol><p>从下图就可以看出,每个环节相比之前都是提前的。敏捷的目的是能够让团队拥抱变化,快速响应。</p><p><img src="/img/bVcQOir" alt="Screen_Shot_2021-03-12_at_14.26.45.png" title="Screen_Shot_2021-03-12_at_14.26.45.png"></p><h2>敏捷开发改进</h2><h3>分支改进</h3><p>原来的分支管理比较混乱,可能造成的问题已经在前面分析过了。</p><p>这里就说说分支管理的最佳实践是什么。</p><ol><li>现在业界普遍都采用了git flow,具体怎么做可以Google一下,网上有太多文章讲这个,我就不赘述了。这里就展示一张git flow的全景图。<br><img src="/img/bVu5lo" alt="git-workflow-release-cycle-4maintenance.png" title="git-workflow-release-cycle-4maintenance.png"></li><li><p>每次git的commit message的推荐格式:Card ID: message</p><ol><li>Type主要有:feature, refactor, fix等。</li><li>每次git的commit推荐能关联到每个故事卡的卡号,这样方便追溯每个故事卡相关的改动。现在很多工具都支持通过message的卡号直接找到对应的故事卡,反之亦然。</li></ol></li></ol><h3>CI/CD</h3><ul><li>从代码的生命周期开始,CI/CD是保证每个环节快速流转的基础,同时也是快速获取反馈的途径。</li><li>而CI的基础则是自动化测试,比如最基本的单元测试。</li><li><p>每完成一张故事卡,理论上都可以持续部署到生产环境。而不应该等待所有需求都完成了,或者等前后端都完成了,再做上线部署。</p><ul><li>部署的步子越小,回滚的成本也就越低。</li></ul></li></ul><h2>总结</h2><p>图里的敏捷开发一定比上面的小瀑布快吗?不一定,这里还有几个因素是需要考虑的。</p><ol><li>需求A拆解成story是有成本的。根据产品经理或BA的能力不同,以及需求复杂度的不同,拆卡花的时间也不同。</li><li>小瀑布模式里面,在没有bug的情况下只会测试一次。在敏捷模式下,相比原来的小瀑布,会针对story1和story2做回归测试。因此增加了测试时间。</li><li>另外,决定敏捷开发能否运行很好的因素还有很多,只有不断探寻最佳实践,持续改进,才能无限逼近我们期望的状态。</li></ol><p>总结起来,敏捷是通过小步快跑的方式,提升了响应变化的速度,以达到提升整体交付速度与质量的目的。</p><p>本文只是通过一个真实的客户案例,来分析如何基于当前现状做敏捷改造,本文并没有写完全部敏捷改造的内容,因为敏捷包含的内容实在太多了,如果大家感兴趣可以给我留言,后面我会继续分享这个案例中涉及到的其他敏捷改造。</p>
敏捷改造(上):真实案例研发过程分析
https://segmentfault.com/a/1190000039697837
2021-03-23T20:19:20+08:00
2021-03-23T20:19:20+08:00
Woody
https://segmentfault.com/u/woodyyan
1
<h2>背景</h2><p>最近我去一家科技公司做敏捷咨询,通过梳理该公司的研发过程,发现了该公司的研发过程中许多可以改进的地方,于是我便记录下来,与大家分享学习。</p><p>本文会剖析该公司的研发过程,把每个环节详细分析一遍,以找出研发过程中的问题和可以改进的地方。然后再讲解如何做敏捷改造。</p><h2>研发过程分析</h2><h3>全景图</h3><p>下图是该公司的研发全景图,从时间线来看,上面一条时间线可以看出整个需求流转的生命周期,下面一条时间线可以看出整个代码流转的生命周期。</p><p><img src="/img/bVcQJnp" alt="研发过程" title="研发过程"></p><p>下面我们就把每个环节拆开来仔细分析一下。</p><h3>需求管理</h3><p><strong>现状</strong></p><p>现在是通过共享Excel来管理所有的需求,业务方在表格里面填写想要的需求,包括新需求或bug等,并为每个需求生成一个序号方便追踪。</p><p>大概长下图的样子。这是非常典型的传统需求管理方式。</p><p><img src="/img/bVcQJnu" alt="需求管理" title="需求管理"></p><p><strong>问题</strong></p><ol><li>大颗粒的需求可以这样管理,但是不能所有阶段都这样管理,会造成需求粒度太大,细节太多,边界太模糊。</li><li>如果不做story拆分,这样的需求离能开发还有很多空间,需要做拆分、细化、转化,最后才能开发。</li><li>这样的需求表格缺乏很多细节,比如UI长什么样子,某个业务逻辑有多少条分支等。</li><li>这样的表格无法知道业务方和研发方对需求的理解是否一致,很容易出现返工。</li><li>此类表格管理需求,不便于业务方追踪需求进度和状态,以及可视化需求的转化过程。</li></ol><h3>需求评审</h3><p><strong>现状</strong></p><p>产品经理会和业务方一起开会,针对表格里面的某个需求,来确定这个需求的细节,以及怎么做。确保双方的理解是一致的。</p><p><strong>问题</strong></p><ol><li>需求评审会的时候没有记录过程中确认的结论,导致会后大家又忘记当时的结论是什么。</li><li>由于需求粒度过大,很多细节无法详尽的确认清楚,容易导致返工。</li><li>由于需求粒度过大,需要比较长的时间来完成需求评审,通常会花2小时以上。</li><li>没有sign off,无法判定需求是否通过了业务方的认可。</li><li>需求澄清是一个随时随地的动作,但该公司缺乏能随时做需求澄清的氛围或文化。</li></ol><h3>产品设计</h3><p><strong>现状</strong></p><p>拿到需求后,产品经理会根据需求以及和业务方的沟通,达成一致后开始设计产品文档,把需求涉及到的原型图,业务逻辑等全部画到产品文档上,以提供给开发人员进行开发。</p><p><strong>问题</strong></p><ol><li>需求通常都很大,产品经理很少把需求拆分成story,也很少在JIRA等工具上拆卡建卡来管理所有的需求。导致产品设计周期很长,细节很多,无法一次性考虑全面。</li><li>产品经理设计产品文档的时候,通常是自己设计,设计好了再给业务方或者开发看。没有频繁反馈和需求澄清,导致需求可能被脑补,并不是业务方想要的。</li><li>产品文档目前是用版本管理工具来管理的,比如git,不便与查找和归档。</li><li>需求、产品文档、代码没有关联关系,不方便后期查找某个需求相关的产品文档和代码。</li></ol><h3>技术评审</h3><p><strong>现状</strong></p><p>目前,产品经理设计完成后,会拉上开发和业务一起进行技术评审,确保设计的产品文档三方能达成一致。</p><p><strong>问题</strong></p><ol><li>由于需求太大,评审时间太长,通常超过2小时,久而久之大家会越来越反感这样的评审会议,并且会议后期大家的注意力也不集中了。</li><li>细节太多,容易忽略某些细节,导致最后开发依然有不确定的开发细节,并且开发的结果和业务方的期望不匹配。</li></ol><h3>开发</h3><p><strong>现状</strong></p><p>拿到产品文档之后,后端会根据文档中的业务逻辑,开发完成服务端的功能,前端会根据文档中的原型图或者高保真UI设计图,开发完成客户端的功能。</p><p>再来说说该公司的分支管理模式。</p><p>他们把分支分为了:线上分支(<code>master</code>),测试分支(<code>stable</code>),开发分支(<code>dev</code>)等。</p><p>保证不同的分支做不同的事情,防止分支污染。</p><ol><li>线上分支(<code>master</code>):是预上线环境和线上环境的分支,以这个分支为准,其他分支都是以这个分支为基础拉取。</li><li>测试分支(<code>stable</code>):测试环境分支,是给测试团队测试使用,如果有些功能在本地及开发不容易测试,开发人员可以到测试分支进行自测。</li><li>开发分支(<code>dev</code>):开发人员自测。</li></ol><p>分支命名规范:姓名+需求名+日期</p><p>分支会根据上线需要,merge到stable进行测试,或者merge到master进行上线。如下图。</p><p><img src="/img/bVcQJnv" alt="分支管理" title="分支管理"></p><p><strong>问题</strong></p><ol><li><p>分支管理混乱,每个分支既可能合并到dev,也可能合并到master,原因是因为这样可以解决仅部分功能要上线的问题,哪个功能要上线,就合并哪个分支到master。</p><ol><li>理论上,拉了分支开发的代码都是应该要上线的,不上线的代码会浪费开发资源。</li><li>分支开发的时间也不应该太长,太长会导致代码冲突变严重,回滚成本变高。</li><li>如果是因为测试没做完而暂时不上线,那可能是因为分支所代表的功能粒度太大了,测试时间太长,应该从源头开始拆解需求。</li><li>如果是因为业务变更而暂时不上线,应该使用feature toggle来解决。</li></ol></li><li>功能分支虽然写了开发者名字和需求名,但依然很难关联具体的需求是哪一个。</li><li>虽然规定了从master拉取分支,但大家有的从dev拉取,有的从stable拉取,没有统一规范。</li><li>分支命名中的日期意义不大,因为分支理论上存在的时间应该尽量短,才能避免更多的冲突,减少review的工作量,以及减少回滚的成本。其次分支拉出来的时间在git上都能清晰的看到。</li><li>开发很少做需求澄清,会按照自己的想法实现某个需求,遇到不确定的地方没有和团队讨论,没有找产品、业务确认。会导致最终实现和业务方的期望不匹配。</li><li>没有code review,无法统一开发团队成员对代码的规范,无法及时发现代码中的问题,无法做代码层面的知识传递。</li><li>没有写单元测试,无法做到研发自测与质量内建,无法保证代码的正确性,无法保证其他人不会破坏原有代码功能,无法持续集成。</li><li>没有CI/CD,无法及时获取反馈,无法快速部署,无法快速发现问题。</li></ol><h3>提交测试</h3><p><strong>现状</strong></p><p>开发完成开发工作之后,自己测试通过了之后,会交给测试人员进行测试。测试人员在提测之前会根据产品文档先写测试用例。</p><p><strong>问题</strong></p><ol><li>提测的过程靠口头传递,测试人员无法可视化的知道开发进度,做了哪些改动,可以部署哪个环境,使用哪个版本。</li></ol><h3>测试</h3><p><strong>现状</strong></p><p>测试会根据写好的测试用例对功能进行测试,如果发现问题,会返回给开发,让开发修复。</p><p><strong>问题</strong></p><ol><li>测试用例目前是用单独的工具来管理,没有和需求关联起来。</li><li>测试完成之后,没有对业务方的showcase,无法获取业务方的验收反馈。</li></ol><h3>上线</h3><p><strong>现状</strong></p><p>每个需求基本上都包含了前后端,因此会等前后端都开发测试完成后,再一起做上线。</p><p><strong>问题</strong></p><ol><li>上线内容比较多,一旦出了问题,会导致回滚成本比较高,定位问题比较慢。</li><li>上线时间比较慢,不能让业务方快速看到最终的功能。</li></ol><h2>总结</h2><p>这样的研发过程梳理完了之后,会发现其实这样的过程就是我们俗称的小瀑布。它的特点是相比传统的瀑布模式它更轻量级,但相比敏捷,它又更重量级。目前很多公司都在采用这样的小瀑布模式。</p><ul><li>小瀑布模式的缺点在于它的沟通成本、等待成本、返工成本依然很高,还有可以优化的空间。</li><li>同时整个过程中,需求评审、技术评审、用例评审都做得比较重,每次评审的内容都非常多,时间非常长,细节非常多。</li><li>整个过程中的所有产出物并没有明确的关联关系,也没有统一的管理工具和存储位置,随着时间的推移,所有知识管理将变得越来越难,新人的学习成本将变得越来越高。软件项目中的信息量会在潜移默化中变成异常高的复杂度。</li><li>环节与环节之间没有文字记录明确一个环节的结束与开始,比如开发到测试。基本上是靠成员之间的口头传递。</li><li>最后还发现该公司不是全功能团队模式,而是按角色分的,一个角色可能会同时负责几个项目,比如A开发上午在写X项目的代码,下午可能在写Y项目的代码了。</li></ul><p>根据这些现状问题,具体怎么改造,将在下篇来具体讲解。</p>
单元测试的一些分享
https://segmentfault.com/a/1190000039684974
2021-03-22T10:52:44+08:00
2021-03-22T10:52:44+08:00
Woody
https://segmentfault.com/u/woodyyan
0
<h2>背景</h2><p>最近在给一个客户做技术咨询,然后发现了客户对于单元测试的一个有意思的现象。分享出来,大家一起学习探讨一下。</p><h2>现状分析</h2><p>这里以java后端项目例,发现客户写的测试长下面的样子。(代码已经脱敏处理过。)</p><pre><code class="java"> @Autowired
private SampleJob handler;
@Test
public void testStart() throws Exception {
SampleParamVo paramVo = new SampleParamVo();
paramVo.setStartTime("2021-03-18");
paramVo.setEndTime("2021-03-18");
handler.execute(paramVo);
} </code></pre><pre><code class="java"> @Autowired
private SampleHandler handler;
@Test
public void testHandler() {
handler.doHandler(new DateTime("2021-11-26"), null);
} </code></pre><p><strong>那么这样的测试代码有什么问题呢?</strong></p><ol><li>别人看不懂这个测试是在做什么。首先测试的方法名没有任何意义,其次测试代码也只是调用了某个函数。</li><li>无法运行。这类测试代码运行往往需要启动其他服务或者需要一些特殊的设置。无法运行就意味着它不能成为CI跑测试的一部分。</li><li>没有断言。没有断言就无法知道测试的代码的正确性。</li><li>使用了<code>@Autowired</code>这样的代码,增加了测试的耦合以及编写成本。</li></ol><p>和客户深聊了之后发现,原来客户不同的人对单元测试的理解也不一样。</p><ul><li>写这个代码的开发人员说,“这些代码是在开发完成之后做一些自测的辅助脚本。”</li><li>有的开发人员说,“我们是微服务,单元测试需要调用其他服务,写起来很麻烦,而且如果其他服务不可用时,测试也跑不过。”</li><li>测试人员说:“单元测试我们有的,我每天都在写测试用例,到单元测试的时候我就会把我的用例全部过一遍。”</li></ul><p>所以我们可以发现,有的开发人员口中的单元测试其实应该属于集成测试或者E2E测试,有的开发人员完全没有写过单元测试,而测试人员理解单元测试是自己手动测试的时候用的测试用例。</p><p>那我们就先来说说什么是单元测试。</p><h2>什么是单元测试?</h2><blockquote>单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。</blockquote><p>通常在java的世界里面,单元测试就是指对一个<code>public</code>的方法编写检查和验证的代码。</p><h2>为什么要写单元测试?</h2><p>写单元测试主要有两大目的:</p><ol><li>验证功能实现。</li><li>保护已有功能不被破坏。</li></ol><p>当我们写完一个方法,我们如何知道自己写的方法是按期望工作的呢?这个时候就可以添加单元测试来验证我们的代码是按期望工作的。即当我们给定指定的输入,我们获得期望的输出,则我们说这个功能是符合期望的。</p><p>其次,代码不是写了就永远不变的,当需求变更时,新增需求时,修复bug时,都会修改代码,而单元测试则能保护我们已有的功能不被破坏。保护已有功能不会被自己破坏,被新人破坏,被新功能破坏。</p><h2>如何写单元测试?</h2><p>下面是一个单元测试的例子</p><pre><code class="java"> @Test
public void should_return_fizz_given_input_can_be_divided_by_3() {
FizzBuzz fizzBuzz = new FizzBuzz(); // Given
String actual = fizzBuzz.sayIt(6); // When
Assertions.assertEquals("Fizz", actual); // Then
} </code></pre><p><strong>一个标准的单元测试包含以下几个部分:</strong></p><ol><li>能描述清楚做了什么的测试名(方法名)</li><li><p>单元测试的Given、When、Then具体内容。</p><ol><li>Given:初始状态或前置条件</li><li>When:行为发生</li><li>Then:断言结果</li></ol></li></ol><p><strong>写好单元测试要主要几个要点:</strong></p><ul><li>因为测试代码并不会进入生产环境,同时我们期望测试即文档,因此测试的名称写很长也没有关系,重要的是能清晰的表达我们这个测试所覆盖的用例是什么。</li><li>一个测试只测一种case。</li><li><p>单元测试通常需要覆盖大量的case来保证我们的代码在绝大多数场景下都是按期望工作的。因此要做到这一点可以参考下面两大原则。这里就不详细讲解这两个原则,具体内容可以Google。</p><ul><li>CORRECT原则</li><li>Right-BICEP原则</li></ul></li><li><p>单元测试有一个考核的标准就是测试覆盖率,指的是我们的代码有百分之多少被单元测试测到了。</p><ul><li>测试覆盖率分几种:行覆盖率,分支覆盖率,路径覆盖率,条件覆盖率等。每种都可以单独设置百分比。通常我们会看中行覆盖率和分支覆盖率。</li><li>通常行业里面常设置测试覆盖率在85%以上。</li><li>为什么不是100%?因为不是所有代码都能被测到的,比如private的构造函数是无法被测到的,这种就会降低覆盖率。</li></ul></li><li>通常所有的自动化测试都是开发人员来写,比如单元测试,集成测试等。</li></ul><h3>测试金字塔</h3><p>说到单元测试,就不得不提测试金字塔,如下图,最底层是单元测试,最顶层是UI测试。(测试金字塔有好几种,但道理都是相通的)</p><p>看左边的箭头,越往下越快,越往上越慢,它主要包括编写越快,运行越快,定位问题越快等。</p><p>看右边的箭头,越往下成本越低,越往上成本越高,包括时间成本,金钱成本,人员成本,维护成本等。</p><p><img src="/img/bVcQF2i" alt="测试金字塔" title="测试金字塔"></p><h2>什么是mock?</h2><p>我们在做单元测试的时候,常常可能访问外部系统或者外部类,这些外部的不可控性会让我们的单元测试成本变得很高。</p><p>常见的外部不可控性有:HTTP访问,增删文件,随机性,时间相关性,接口类等。</p><p>于是开发者便开始探索更廉价的方式来写单元测试,mock就是其中的解决方案。</p><p>mock 对象运行在本地完全可控环境内,利用 mock 对象模拟被依赖的资源,使开发者可以轻易的创建一个稳定的测试环境。</p><p>mock是<a href="https://link.segmentfault.com/?enc=c730uQ2zJ3gohAs99a0VQA%3D%3D.5ukGIPWuKPvdcknHevR5BYDyNLDlcSL3cK%2FVeiLOTdO2v5THIJN2ewQEnhXl35DG7yKOabzuvXM5D%2F5udg38tw%3D%3D" rel="nofollow">Test double</a>理论中的一种,如果对test double理论感兴趣,可以到<a href="https://link.segmentfault.com/?enc=lXFu8XD%2BUtT6ohHjfnitKg%3D%3D.X2hqaq4JXSwZO0ueWQJ6MmirohQ7FvAT8%2BbnRJVdYc2Qa%2B7Q8WdbclYcEPrt3kv%2FPf5QwRRzDutbJbYwAj9%2B7A%3D%3D" rel="nofollow">这里</a>了解更多,这里就不展开说了。</p><h3>如何用mock?</h3><p>还是以java为例,java的世界中常用的mock框架比如mockito。</p><p>下面是一个mock的例子。</p><pre><code class="java"> @Test
void should_return_100_when_get_list_size() {
List map = mock(List.class);
//当调用list.size()方法时候,返回100
when(map.size()).thenReturn(100);
Assert.assertEquals(100, map.size());
} </code></pre><p>单元测试是我们测试的最小单位,因此我们只测当前这个<code>public</code>的方法中的实现,而方法中调用第三方类的东西,我们都应该mock掉。</p><p>这样的好处有两个:</p><ol><li>不会因为其他类的不可控性而导致这个测试方法变得难写。</li><li>其他类的修改不会导致这个测试方法挂掉。所有的变化都被隔离出去了。</li></ol><h2>什么是TDD?</h2><p>最后再升华一下,简单说一说TDD,TDD的全称是Test driven development,即测试驱动开发。它是极限编程XP中的一个标准实践。</p><p>TDD要求在编写某个功能的代码之前先编写测试代码,然后只编写使测试通过的功能代码,通过测试来推动整个开发的进行。</p><p>这样做有四大好处:</p><ol><li>TDD是一个很好的契机,可以让你在考虑解决方案之前先考虑问题。</li><li>首先考虑测试会迫使你首先考虑与代码的接口。先思考接口可以帮助你将接口与实现分开。</li><li>简单设计。</li><li>几乎100%的测试覆盖率。</li></ol><p>这里我就不详细叙述TDD相关的话题了,因为TDD是一个比较大的话题,如果感兴趣,下次专门开一个新话题来聊TDD。</p>
能力识别模型
https://segmentfault.com/a/1190000039290532
2021-02-26T14:09:11+08:00
2021-02-26T14:09:11+08:00
Woody
https://segmentfault.com/u/woodyyan
1
<h2>背景</h2><p>这些年一直在做对外的敏捷开发培训,也就是针对其他企业的开发人员进行敏捷全栈开发培训,经过培养之后期望这些开发人员能快速胜任工作。</p><p>但是由于大部分的培训最终产出物可能都是一些评价或者一个分数,它并不能很好的反应一个人的能力情况,也不能帮助培训者或者企业更好的识别每个人的能力。</p><p>因此,经过多次迭代,归纳总结出了一个针对开发岗的能力识别模型,目的是能够帮助培训者和企业更好的识别他们的能力分布。</p><h2>能力识别模型是什么</h2><p>能力识别模型是一个包含<strong>4种</strong>能力,<strong>32种</strong>抽象行为的一个模型。</p><p>用于识别开发岗的开发人员的各纬度能力分布。</p><p>4种能力分别是:</p><ol><li>技术能力</li><li>学习能力</li><li>理解能力</li><li>沟通能力</li></ol><p>每种能力对应了8种抽象的相关行为,共32种行为。</p><p>下面就展开详细说一下。</p><h3>技术能力</h3><p>顾名思义,技术能力就是指和开发技术相关的各种能力。</p><p>它包含的下面8种抽象行为指的是他/她会在过程中使用到或者被观察到能体现这些行为的facts或事实。</p><p><strong>1 - 验收思想</strong></p><p>验收思想指的是做事情或者编码时能考虑到如何验收。</p><p>比如写测试是一种验收,tasking时标明输入和输出是一种验收。</p><p><strong>2 - 代码设计</strong></p><p>代码设计指的是写代码的时候有代码设计。</p><p>比如使用设计模式,比如在Java里面使用stream API,比如有良好的OO设计,比如遵守了SOLID原则。</p><p><strong>3 - 独立编码</strong></p><p>独立编码指的是能独立编写代码。</p><p>比如独立完成Java编程,独立完成react编程,独立完成python编程等。</p><p><strong>4 - 整洁代码</strong></p><p>整洁代码指的是编写的代码满足clean code。</p><p>比如没有明显的坏味道等。</p><p><strong>5 - 完成任务</strong></p><p>完成任务就是指的能按时合格完成任务。</p><p>比如按时合格完成编程练习,比如按时合格完成非技术的画图工作等。</p><p><strong>6 - 解决问题</strong></p><p>解决问题指的是能解决遇到的各种问题,包括技术问题,非技术问题等。</p><p>比如能使用debug修复遇到的bug,比如能通过看日志或者搜索等解决遇到的技术问题。</p><p><strong>7 - 利用资源</strong></p><p>利用资源指的是能利用一切资源来完成任务。</p><p>比如向教练求助,向同学同事提问,上网搜索,使用工具等都属于利用资源。</p><p><strong>8 - 探索新技术</strong></p><p>探索新技术指的是自己能探索一些新的技术,包括框架,工具,算法等。</p><p>比如学习了某种新算法,研究了某个新工具或者新框架,比如没接触过Jenkins但自己研究了如何使用Jenkins,比如没写过python但学习python解决了某个问题。</p><h3>学习能力</h3><p>顾名思义,学习能力就是通过不断学习来完善自身的能力。</p><p>它包含的下面8种抽象行为指的是他/她会在过程中使用到或者被观察到能体现这些行为的facts或事实。</p><p><strong>1 - 迭代思想</strong></p><p>迭代思想指的是做任何事情都能小步快跑,迭代式的完成任务。</p><p>比如写代码的时候能够小步提交,比如做项目时能迭代开发。</p><p><strong>2 - 遵循最佳实践</strong></p><p>遵循最佳实践指的是做任何事情都能遵循最佳实践。</p><p>比如遵循重构的最佳实践,比如遵循code review的最佳实践,比如遵循TDD的最佳实践,比如遵循站会的最佳实践。</p><p><strong>3 - 从他人身上学习</strong></p><p>从他人身上学习指的是能学习他人的优秀的技术、习惯和思想。</p><p>比如会向他人请教如何做代码设计,比如能学习别人是如何组织站会的,比如code review时能学习别人更好的代码实践。</p><p><strong>4 - 每日总结</strong></p><p>每日总结指的是每天都能坚持总结一天的所得和所缺,类似于一个人的retro,来帮助自己回忆所学和改善不足。</p><p>比如每日会写总结日志,比如每次获得新知识会记笔记,比如经常写博客。</p><p><strong>5 - 执行力</strong></p><p>执行力指的是完成预定目标的操作能力。</p><p>比如code review之后马上就能重构自己的代码,比如获得任务之后马上就能开始计划,比如执行任务的时候没有拖延症。</p><p><strong>6 - 优先级</strong></p><p>优先级指的是做任何事情都能分清优先级,优先完成优先级高的任务或者环节。</p><p>比如做项目的时候能优先完成核心功能而不是选择自己喜欢的功能做,比如编码的时候能优先完成核心功能而不是纠结某个非核心算法。</p><p><strong>7 - 工作习惯</strong></p><p>工作习惯指的是有良好的工作习惯。</p><p>比如编码时能使用快捷键提高工作效率,比如能使用自动化流程来提高效率。</p><p><strong>8 - 持续改进</strong></p><p>持续改进指的是每天都会根据反馈或者自己总结而持续不断的改进自己的各方面能力。</p><p>比如重构就是一种持续改进,比如不断改善站会体验就是一种持续改进,比如额外练习自己不熟悉的编程技术也是一种持续改进。</p><h3>理解能力</h3><p>理解能力是一种比较综合的能力,它包含了多种综合行为。</p><p>把理解能力拆解一下,也包含了下面8种抽象的行为。</p><p><strong>1 - 任务分解</strong></p><p>任务分解指的是做事情之前会先tasking,或者会把复杂的任务先列出执行步骤。</p><p>比如TDD时先做tasking,比如要调研一个复杂技术时先理清要调研的每个步骤。</p><p><strong>2 - 接受反馈</strong></p><p>接受反馈指的是能接受他人基于事实的反馈并改进。</p><p>比如code review时别人对代码提出的更好的建议能接受并重构。</p><p><strong>3 - 需求澄清</strong></p><p>需求澄清指的是拿到任务或者需求时都能先做需求澄清,避免产生二义性。</p><p>比如做编程练习的时候能澄清所有模糊的描述,而不是自己想象应该是什么样的需求。比如设计功能的时候能和用户以及团队讨论功能需求而不是自行决定。</p><p><strong>4 - 理解需求</strong></p><p>理解需求指的是能理解每次练习的需求,能理解别人的提出的需求。</p><p>比如编程结果里面没有偏离需求的实现。</p><p><strong>5 - 发现他人的问题</strong></p><p>发现他人的问题指的是能发现他人代码中的问题,或者敏捷实践中的问题。</p><p>比如能发现他人代码中不合适的命名,比如能发现他人代码中的逻辑错误,比如能发现他人在敏捷活动中的错误实践。</p><p><strong>6 - 理解新知识</strong></p><p>理解新知识指的是能理解学到的所有新知识,包括技术知识,敏捷知识以及业务知识。</p><p>比如能理解新框架的使用方式,比如能理解新工具的使用场景,比如能理解新的敏捷活动的最佳实践。</p><p><strong>7 - 版本管理</strong></p><p>版本管理指的是会使用GIT等版本管理工具,并提交有意义的commit。</p><p>比如git commit的描述清晰记录了团队要求的所有信息。比如在创建数据库时也会使用数据库版本管理工具。</p><p><strong>8 - 业务命名</strong></p><p>业务命名指的是在代码中或者故事卡中,都能使用有业务意义的名字。</p><p>比如不会出现技术命名,或者毫无含义的命名。比如能在所有编程场景中统一语言。</p><h3>沟通能力</h3><p>沟通能力指的是沟通、表达、团队协作等软实力相关的能力。</p><p>它同样包含了下面8种抽象行为。</p><p><strong>1 - 提供帮助</strong></p><p>提供帮助指的是在团队中能积极主动的向团队提供支持或帮助。</p><p>比如帮助团队攻克技术难题,比如帮助成员fix某个bug,比如主动承担某个任务。</p><p><strong>2 - 积极讨论</strong></p><p>积极讨论指的是能积极参与团队的讨论和决策。</p><p>比如code review的时候能积极的参与讨论,比如需要某个决定的时候能积极说出自己的想法。</p><p><strong>3 - 团队协作</strong></p><p>团队协作指的是能积极促进团队正向的成长和前进,体现自己的协作精神。</p><p>比如能互相激励完成某个任务,比如能共享资源来帮助团队沉淀知识,比如能取长补短帮助团队前进,比如能组织管理团队的相关事务。</p><p><strong>4 - 有效对话</strong></p><p>有效对话指的是能在和别人的沟通中产生有效对话。</p><p>比如没有多余的废话,或者不会出现沟通完之后依然没有得到答案。</p><p><strong>5 - 回答问题</strong></p><p>回答问题指的是在工作和学习中能积极的回答问题。</p><p>比如教练问的问题,同学同事提的问题等。</p><p><strong>6 - 寻求帮助</strong></p><p>寻求帮助指的是在遇到困难的时候能积极的寻求帮助,而不会因为个人原因阻碍团队或者项目的前进。</p><p>比如遇到不懂的编程问题就直接提问,比如遇到不懂的知识就提问,比如遇到解决不了的技术难题就寻求他人或者网络的帮助。</p><p><strong>7 - 给出反馈</strong></p><p>给出反馈指的是能在团队中积极的给他人反馈,帮助他人成长,帮助团队成长。</p><p>比如在code review中指出他人的代码坏味道,比如在团队活动中给他人给出反馈帮助他人成长。</p><p><strong>8 - 分享</strong></p><p>分享指的是能积极分享自己的想法或者技能。</p><p>比如在站会中分享业务,在code review的时候分享自己学的新技术等。</p><h2>如何使用能力识别模型</h2><p>能力识别模型可以被设计成一个二维表格,每种能力对应8种行为,每种行为有1-10分,每种行为默认每个人都具备这些能力,所以默认5分。</p><p>如果该行为主动做到所有人中的最好就是10分,如果该行为没有做到或者做得不好就相应扣分。</p><p>同时,每种行为都需要观察记录facts,基于事实来支撑打分。所以它大概会长下图的样子。<br><img src="/img/bVcO1pY" alt="能力识别模型" title="能力识别模型"></p><p>根据这些数据,最终就可以生成这样一张直观的能力雷达图。</p><p>通过雷达图我们就可以直观的看到每个人在不同的能力纬度上的优势和不足,以更有针对新的帮助这个人的成长。<br><img src="/img/bVcO1p8" alt="能力识别模型雷达图" title="能力识别模型雷达图"></p><p>有了这些数据,还可以从多维度去对比不同的人之间的能力差异,获取不同的数据视图,帮助团队更好的定位人才的发展。</p><h2>未来</h2><p>这个能力识别模型并不是完美的,它还需要不停的迭代优化,适配各种不同场景的抽象行为。</p><p>它目前只是用于帮助企业了解开发人员的一种可视化形式。</p><p>未来,有了这个能力识别模型,可以根据不同团队的需要,生成不同的数据视图,来辅助团队的发展。</p><p>最后,如果对于这个能力识别模型有任何想法或者建议,欢迎与我讨论。</p>
在线培训工具合集
https://segmentfault.com/a/1190000021793283
2020-02-20T11:52:43+08:00
2020-02-20T11:52:43+08:00
Woody
https://segmentfault.com/u/woodyyan
0
<h2>引言</h2>
<p>上次总结了一些在线培训的经验,这次给大家总结一些在线培训的工具。</p>
<p>有一些常见必备工具就不仔细介绍了。</p>
<p>比如微信,用于群聊讨论发资料等等。</p>
<p>比如钉钉,用于群聊讨论发资料等。但不太推荐用钉钉做在线视频会议培训,因为它的视频会议功能太简单,下面看了zoom的介绍大家就知道了。</p>
<p>比如GitHub,用于技术培训的代码保存和传递。</p>
<p>比如iCloud,WPS,石墨文档,腾讯文档等,用于在线协作场景。</p>
<p>为什么没有推荐Office,因为Office是收费的,不是所有人都愿意付费使用Office,另外Office的云都没有其他的快,会影响线上效果。</p>
<p>那么这里就重点给大家介绍以下几个非常适合在线培训的工具。</p>
<ul>
<li>PowerPoint</li>
<li>Keynote</li>
<li>Mural</li>
<li>Miro</li>
<li>Zoom</li>
<li>Visual Studio Code</li>
</ul>
<h2>PowerPoint</h2>
<p>俗称PPT,通常讲课都需要用PPT来教授知识,在线培训也不例外。大家对PPT的使用也很熟悉了。这里只介绍PPT的两个适合在线培训的功能。</p>
<h4>第一个功能:实时字幕</h4>
<p>实时字幕就是,你在通过PPT讲解的时候,PPT可以实时的识别你说的话,并以字幕的形式显示在屏幕下方。如下图所示。</p>
<p>这个字幕识别支持多种语言,包括英文和中文。而且速度非常快。</p>
<p><img src="/img/bVbDBwy" alt="Screen Shot 2020-02-18 at 7.35.46 PM.png" title="Screen Shot 2020-02-18 at 7.35.46 PM.png"></p>
<p>怎么打开这个功能呢?</p>
<p>在PPT演示模式,把鼠标移到屏幕左下角就会出现如下图的图标,第三个图标就是打开字幕功能的开关。<br><img src="/img/bVbDBwz" alt="unnamed.png" title="unnamed.png"></p>
<p>这样功能有两个好处:</p>
<ol>
<li>当网络不稳定的时候,有时候听课的人有个别关键字没听清楚,他只需要看字幕就知道刚才说了什么,不用打断讲师再问一遍。</li>
<li>当屏幕在一页PPT上长时间不动的时候,可以让学生聚焦在不停变化的字幕上,不至于盯着不变的屏幕而枯燥犯困。</li>
</ol>
<h4>第二个功能:笔</h4>
<p>笔有两种,一种是激光笔,另一种是可以画画的笔。</p>
<p>先说激光笔,激光笔也就是你在现场培训的时候,很多讲师手里会拿一个红外激光笔,可以在投影屏上打出一个红色的亮点,告诉听众现在讲解的重点是哪里。</p>
<p>那么PPT自带这个功能,它能让你的鼠标变成一个激光笔,如下图所示。这样你就可以通过鼠标来不断指示目前讲解的重点是什么。</p>
<p><img src="/img/bVbDBwI" alt="Screen Shot 2020-02-18 at 7.37.47 PM.png" title="Screen Shot 2020-02-18 at 7.37.47 PM.png"></p>
<p>有人会问,那就用原始鼠标箭头不也能做同样的事情吗?</p>
<p>首先,在PPT演示的时候,鼠标长时间不动是会自动消失的。</p>
<p>其次,当你移动鼠标的时候,听众不知道是你在划重点,还是在随意移动鼠标。而激光笔的出现则是在暗示听众,我是在划重点。</p>
<p>然后说说可以画画的笔,如下图所示。</p>
<p>这种笔可以帮助讲师在讲课的时候勾勒重点,或者在PPT上直接画图辅助讲解。</p>
<p><img src="/img/bVbDBwJ" alt="Screen Shot 2020-02-18 at 7.38.29 PM.png" title="Screen Shot 2020-02-18 at 7.38.29 PM.png"></p>
<p>怎么打开这个功能呢?</p>
<p>在PPT演示模式,把鼠标移到屏幕的左下角就会出现下面的几个图标。<br><img src="/img/bVbDBwR" alt="unnamed (1).png" title="unnamed (1).png"></p>
<p>选择第二个笔的图标,就可以选择不同类型的笔,笔还可以选择不同的颜色哦。<br><img src="/img/bVbDBw3" alt="Screen Shot 2020-02-18 at 7.40.13 PM.png" title="Screen Shot 2020-02-18 at 7.40.13 PM.png"></p>
<h2>Keynote</h2>
<p>Keynote没有激光笔,也没有其他笔。</p>
<p>但它可以利用iPhone手机来遥控keynote,同时在iPhone上就能使用白板和激光笔。</p>
<p>当然在线培训时使用iPhone来遥控不是特别合适,所以这里就不展开讲了。</p>
<p>这里重点介绍keynote live这个功能。</p>
<h4><strong>Keynote live</strong></h4>
<p>Keynote live的中文名叫keynote直播,它允许你邀请任何人在他们的电脑或者手机设备上观看你的keynote,重点是,观看者不需要有iCloud账户。</p>
<p>大部分时候我们视频会议要给别人看keynote,都是通过分享屏幕来完成的,而keynote live让我们无需分享屏幕就可以让别人实时看到keynote内容。你只需要把keynote live的分享链接发给观看者,观看者在浏览器打开链接就可以了。</p>
<p>若要开启 Keynote 直播,首先需要将该文稿保存在 iCloud 中,然后在工具栏中找到 Keynote 直播即可。<br><img src="/img/bVbDBxI" alt="Screen Shot 2020-02-18 at 7.42.19 PM.png" title="Screen Shot 2020-02-18 at 7.42.19 PM.png"></p>
<p>点击继续之后可以看到直播设置,在这里可以将链接复制给需要参加的听众,并且可以设置密码访问。</p>
<p><img src="/img/bVbDBxN" alt="Screen Shot 2020-02-18 at 7.42.41 PM.png" title="Screen Shot 2020-02-18 at 7.42.41 PM.png"></p>
<p>当受邀者点击链接进入直播之后,主持者可以在自己的 Keynote 直播菜单旁边看到目前在线人数,可以点击播放,在此 Mac 上预演,或者直接开启直播。</p>
<p><img src="/img/bVbDBxR" alt="Screen Shot 2020-02-18 at 7.43.02 PM.png" title="Screen Shot 2020-02-18 at 7.43.02 PM.png"></p>
<h2>Mural</h2>
<p><a href="https://link.segmentfault.com/?enc=98p9Wf4A50KU4OZxOR5sFA%3D%3D.ZJ%2BheeCP8IZEXzXZfpf82nGg75mOyacMQ%2Bow%2FZgR%2BUI%3D" rel="nofollow">Mural</a>是一个可视化协作的工具。</p>
<p>它主要功能是可以在线贴便利贴,如下图所示。它的便利贴有3种格式,正方形、长方形和圆形,同时每个便利贴的大小都可以调整。让我们可以灵活的使用它。</p>
<p><img src="/img/bVbDBx8" alt="Screen Shot 2020-02-18 at 7.43.45 PM.png" title="Screen Shot 2020-02-18 at 7.43.45 PM.png"></p>
<p>它也内置了多种模版供我们使用,这就让我们在远程协作以及培训的时候可以使用它来进行在线retro,在线IPM,在线DDD培训,在线头脑风暴等。</p>
<p><img src="/img/bVbDByi" alt="Screen Shot 2020-02-18 at 3.06.24 PM.png" title="Screen Shot 2020-02-18 at 3.06.24 PM.png"></p>
<p>重点介绍它的两个比较适合在线培训的功能,计时器和房间。</p>
<h4>计时器</h4>
<p>计时器可以让我们在贴便利贴的时候,可视化的计时,让每个参与的人都能知道还剩多少时间。</p>
<p>在屏幕顶部点击如图中计时器图标,就可以选择任意时间了。<br><img src="/img/bVbDByo" alt="unnamed (2).png" title="unnamed (2).png"></p>
<p>当你计时开始之后,你就可以看到还剩多少时间计时结束。</p>
<p><img src="/img/bVbDByt" alt="unnamed (3).png" title="unnamed (3).png"></p>
<p>这个功能可以帮助我们在线培训的时候,把控时间,同时可视化给每一个参与的人。</p>
<h4>创建房间</h4>
<p>Mural支持创建不同的房间来做不同的事情。</p>
<p>想象一下,当我们线上培训需要分小组做事情的时候,是不是就可以用mural给每个小组创建一个房间,不同的组在不同的房间里面协作,而讲师就可以同时看到每个房间的动态,观察总结大家的协作结果,然后再统一总结分享。</p>
<p><img src="/img/bVbDByz" alt="Screen Shot 2020-02-18 at 7.45.24 PM.png" title="Screen Shot 2020-02-18 at 7.45.24 PM.png"></p>
<p>最后,提醒大家注意一下,Mural不是免费的工具,但是新用户可以免费使用30天,这个免费时间对于大部分培训来说已经足够了。</p>
<h2>Miro</h2>
<p><a href="https://link.segmentfault.com/?enc=DudilZ4KyDGnREohlunCXA%3D%3D.HpzXdSpC3XwSyYDU8w0Swi0V1LSEB7%2FIjvNxt%2BI%2Fg6g%3D" rel="nofollow">Miro</a>是一个和Mural非常类似的工具。他们大部分功能都一样。</p>
<p>Miro的不同在于它的模版更多,比如它可以创建思维导图,创建产品roadmap等。</p>
<p><img src="/img/bVbDByL" alt="unnamed (4).png" title="unnamed (4).png"></p>
<p>Miro也有很多其他Mural没有的功能,但是这些功能在线上培训时帮助并不多,所以就不一一介绍了。</p>
<p>最后想要告诉大家的是,当需要使用在线便利贴的时候,更推荐使用Mural而不是Miro,原因是Miro在国内的使用速度较慢,大大影响线上培训的效果。而Mural在国内的使用是几乎无延迟。</p>
<h2>Zoom</h2>
<p>zoom对于大家来说一定不陌生。但是zoom的很多功能你们真的清楚怎么使用吗?</p>
<p>这里就给大家介绍几个适合在线培训的功能。</p>
<h4>Annotate</h4>
<p>Annotate是当你共享屏幕的时候,你可以打开annotate工具栏,就可以进行各种注解操作了。</p>
<p>它位于共享屏幕之后的顶部工具栏中。<br><img src="/img/bVbDBzO" alt="unnamed (5).png" title="unnamed (5).png"></p>
<p>打开annotate工具栏之后你就可以使用如图所示的各种小工具。</p>
<p><img src="/img/bVbDBzT" alt="unnamed (6).png" title="unnamed (6).png"></p>
<p>比如,你可以使用Text在屏幕上写任意文字。</p>
<p>也可以使用Draw在屏幕上画各种图形,比如可以用方框把你讲解的重点内容框起来。</p>
<p><img src="/img/bVbDBzU" alt="Screen Shot 2020-02-18 at 7.47.32 PM.png" title="Screen Shot 2020-02-18 at 7.47.32 PM.png"></p>
<p>可以使用Stamp在屏幕上标记如图所示的图标。比如当听众有疑问的时候,可以使用问号图标在屏幕中标记出有疑问的地方,也可以用箭头指向有问题的地方,这样讲师就可以进行讲解。</p>
<p><img src="/img/bVbDBzV" alt="Screen Shot 2020-02-18 at 7.47.58 PM.png" title="Screen Shot 2020-02-18 at 7.47.58 PM.png"></p>
<p>还可以使用Spotlight把鼠标变成激光笔。这个功能和PowerPoint那个功能是一样的。</p>
<p>最后,想要提醒大家的是,这些annotate是所有人都可以在屏幕上画,同时所有人都能看到。因此zoom自带的这个工具,就帮助我们解决了很多在线培训时的互动问题,有很大的想象空间。</p>
<h4>举手</h4>
<p>在zoom的participants(参与者)里面,有一个举手的功能。当你打开participants之后,你在下方就可以看到一个Raise Hand(举手)的按钮。</p>
<p>那它有什么用呢?除了上面讲到的,使用Stamp在屏幕上标记疑问的互动方式以外,zoom还允许使用者使用举手的功能在讲师讲课的过程中举手提问。</p>
<p>因为有的培训场景中,讲师是会把所有人强制静音了,这个时候要想和讲师说话,就需要用到这个举手功能了。</p>
<p><img src="/img/bVbDBz8" alt="unnamed (7).png" title="unnamed (7).png"></p>
<h2>Visual Studio Code</h2>
<p>写代码的同学对VS Code都不陌生,它是一个非常好用的IDE,今天要介绍的,是它的一个非常适合在线培训的功能。</p>
<p>很多人远程培训的时候,想要和别人结对编程,都是使用远程控制别人的电脑来进行结对编程。</p>
<p>而Visual Studio Code提供了一个Live Share的功能,Live Share可以让你能够在相同的代码库上快速进行协作,而无需同步代码或配置相同的开发工具,设置或环境。让你可以协作的方式来进行远程结对编程。</p>
<p>使用Live Share需要在VS Code上安装一个微软官方的Live Share插件,如下图所示。<br><img src="/img/bVbDBAb" alt="Screen Shot 2020-02-18 at 3.57.09 PM.png" title="Screen Shot 2020-02-18 at 3.57.09 PM.png"></p>
<p>安装好了之后,在VS Code左下角就会有Live Share的按钮,如下图所示。点击它就可以开始Live Share了,但是使用这个功能需要微软账户或者GitHub账户的支持。</p>
<p><img src="/img/bVbDBAi" alt="Screen Shot 2020-02-18 at 3.55.53 PM.png" title="Screen Shot 2020-02-18 at 3.55.53 PM.png"></p>
<p>开始Live Share之后就如下图所示,可以进行结对编程了。</p>
<p>解决了以前远程控制电脑来远程结对编程时的各种卡顿不流畅。</p>
<p><img src="/img/bVbDBAj" alt="vs-code-ls-session.png" title="vs-code-ls-session.png"></p>
<h2>结语</h2>
<p>使用合适的工具可以帮助我们提高线上培训的效果。</p>
<p>当然,适用于线上培训的工具远不止这些,这里只是做个抛砖引玉,希望大家都可以多分享一些利于我们工作的工具。</p>
如何做好一场线上培训
https://segmentfault.com/a/1190000021732619
2020-02-12T16:20:05+08:00
2020-02-12T16:20:05+08:00
Woody
https://segmentfault.com/u/woodyyan
2
<h2>引言</h2>
<p>随着新冠肺炎疫情的持续,很多企业只能在家办公,这就意味着像ThoughtWorks这种提供服务的公司也只能远程提供服务。<br>那么为了能远程给客户提供效果相当的线上技术培训服务,我们做了一些线上培训的实践,在这里总结了一些经验,与大家分享。</p>
<h2>现场培训流程</h2>
<p>首先,我们做的是技术培训,通常现场的技术培训流程是这样的:</p>
<ol>
<li>通过PPT或者白板讲解技术知识点</li>
<li>引导学员对相关技术知识进行讨论或者分享</li>
<li>讲师现场演示技术细节</li>
<li>学员现场做练习</li>
<li>讲师根据练习结果进行反馈和总结</li>
<li>布置作业</li>
</ol>
<p>知道了培训流程,那么把这样的技术培训迁移到线上,会有哪些变化呢?</p>
<ul>
<li>首先,线上培训会有一部分新增的现场培训没有的内容。</li>
<li>其次,相对于现场培训,线上培训会有一些较大的变化。</li>
</ul>
<p>接下来我们就依次总结总结。</p>
<h2><strong>线上培训新增内容</strong></h2>
<p>线上新增的内容大概包括:线上工具的准备,新规则的建立。</p>
<h4>线上工具准备</h4>
<p>战场变成线上了,那肯定得准备线上适用的工具。原来的比如便利贴,蓝泥之类的物料就用不上了。</p>
<p>那线上需要准备哪些工具呢?</p>
<p><strong>首先,是视频会议工具。</strong></p>
<p>需要选取适合你培训内容的视频工具。比如我们选择的是<a href="https://link.segmentfault.com/?enc=Keb%2B%2BH0BKpbnKwsBVls%2FIg%3D%3D.ysGC4sGtUY3XaBzxcUW9qG9MJuW6CnxrG6miTzAEonM%3D" rel="nofollow">zoom</a>。因为zoom支持多人同时在线视频会议,也支持chat聊天室用于发文字内容,还支持白板。</p>
<p>最重要的是它有两大功能非常适合技术培训:</p>
<ol>
<li>多人同时共享屏幕。当学员在练习时,则让每个学员同时共享屏幕,讲师就实时看到每个学员的练习过程,并给出指导意见。</li>
<li>Annotation。zoom支持使用文字,符号,箭头等工具在讲师共享的屏幕上做标记,这样学员就可以针对屏幕上的某个内容提问。</li>
</ol>
<p><strong>其次,需要一个群聊工具,比如微信。</strong></p>
<p>因为通常培训都需要发送一些资料,群聊工具可以在统一的地方发送和存放这些资料。</p>
<p><strong>最后,还有一些非必须但实用的工具。</strong></p>
<p>比如,VS Code可以实时协作编码,用于结对编程。</p>
<p>一些画图工具用于演示结构图、原理图等,比如<a href="https://link.segmentfault.com/?enc=vWntfv7rkK5gRSC5Fft9Eg%3D%3D.LXjWiTuGow68DScRSdIU%2Bme4%2FH3iuRikDqFY8aPplyY%3D" rel="nofollow">AutoDraw</a>,<a href="https://link.segmentfault.com/?enc=8S695LzZMVNI0oAVevdOQA%3D%3D.KNQ64LXInjHJ8ZVA3J72rr4%2FnWDW%2BQk3SKUtkFdwJn4%3D" rel="nofollow">ProcessOn</a>,甚至PPT等。</p>
<p>一些在线便利贴工具用于Retro或者User journey等,比如<a href="https://link.segmentfault.com/?enc=T66Tz9FXcHit16LexXZYEg%3D%3D.deWEcEpkNXW8ypIX12yeCz%2F0Q9QK5kez2am1lQ3G5LI%3D" rel="nofollow">IdeaBoardz</a>,<a href="https://link.segmentfault.com/?enc=HYn01sx9gcvWtIKRffcAag%3D%3D.XUEUap%2FI7rp6viv7ztpClI7AIiDYA3Q3kdDz2RN7q0g%3D" rel="nofollow">Miro</a>,<a href="https://link.segmentfault.com/?enc=%2BSJPtMnIvhScSL3STjjTkg%3D%3D.ziRhBNV%2FAfv6%2FHdKtZJjvqlfsnGbDCDg7iWpon3uOM8%3D" rel="nofollow">Mural</a>等。</p>
<p>当这些工具都准备好了之后,那么培训就多了一个环节。</p>
<p>就是<strong>介绍这些工具是做什么的,以及在培训过程中如何使用。</strong></p>
<p>比如介绍Zoom的多人共享屏幕如何打开,如何在zoom里面举手等。</p>
<p>上面这些都是软件工具,当然<strong>还需要硬件工具</strong>。</p>
<p>硬件方面就要求讲师有摄像头,麦克风和音响。当然TW的MAC都自带了这些硬件。不过如果学员用的是台式机就不一定了,所以培训前一定要确认好。</p>
<p>最后一点,通常讲师线上讲课都容易忽视的一个点,就是摄像头的视频背景。</p>
<p>很多时候讲师都没有注意过自己的视频效果,大部分讲师的视频都是光线昏暗的,自己脸部黑黢黢的。</p>
<p>昏暗的光线会让听众容易昏昏欲睡。这也是为什么直播主播的画面都非常明亮。试想一下,当你去看主播直播,如果ta的光线昏暗,画面黑黢黢的,你还会给ta刷礼物吗。</p>
<p>所以呀,<strong>有条件的讲师可以在自己面前打开一盏灯,做一个有光环的讲师。</strong></p>
<h4>新规则的建立</h4>
<p>线上培训需要一些线上培训适用的新规则。</p>
<p>这些规则根据不同的培训需要而不同,这里给大家提供一些参考。</p>
<ol>
<li>
<p>讲课时,有问题可以用zoom举手,同时用箭头指向有问题的地方。</p>
<ol><li>鼓励互动,鼓励提问。</li></ol>
</li>
<li>当讲师问有没有问题时,没问题就在chat里面扣1,有问题就2。</li>
<li>每个人都必须打开摄像头。</li>
<li>做练习时,每个人都必须共享屏幕。</li>
<li>讲课必须录屏,以便掉线的人回看。</li>
</ol>
<p>这里特别说明一下,打开摄像头有四个目的:</p>
<ol>
<li>促进学员提高专注度</li>
<li>帮助讲师获取反馈</li>
<li>拉近屏幕两边的距离</li>
<li>提高讲师的信心,因为一直对着屏幕讲课,时间久了讲师会怀疑自己是不是一个人在表演。</li>
</ol>
<h2>线上培训的变化</h2>
<p>下面就给大家总结几点线上培训的变化。</p>
<h4>1.<strong>大部分肢体语言都无用了</strong>
</h4>
<p>曾经你是一个影帝,你可以通过你的一言一行一举一动演绎你想讲授的知识。如今,你只剩一张大脸。大部分知识的传递只能靠嘴说了。这就要求讲师有更高的语言组织和表达能力。</p>
<p>就像岳云鹏一样,满脸都是戏的同时,能用语言抓住观众的注意力。</p>
<p>这里给讲师的一点建议就是,因为网络的不稳定,说话可能听不清,因此讲师讲课的时候<strong>尽可能的做到吐字清晰,语速适当。</strong></p>
<h4>2.过程被隐藏了</h4>
<p>现场培训讲师在讲课的过程中,会有很多流程。比如讲师会先在投影上通过PPT讲解课程,然后走到白板上画一些重要的原理或者知识点,接着打开自己的电脑演示刚才讲的内容,最后让学生自己实践然后讲师到学生中间去走查。</p>
<p>但是换成线上培训,这一切就变了,因为这些切换的过程被隐藏了,学员看到的是,屏幕突然从PPT切换到白板,又突然切换到代码演示。学员稍微一走神,就会导致不知道发生了什么,屏幕内容怎么突然变了。</p>
<p>所以,这里给做线上培训的讲师的建议就是:</p>
<ol>
<li>每次切换屏幕记得口头告知即将要做什么操作。比如可以说:“现在我们切换到白板来画一下刚才的结构图,帮助大家理解。“</li>
<li>在每个环节一开始,就把要进行的操作步骤先可视化列出来。让学员知道接下来要发生的事情的步骤。</li>
</ol>
<h4>3.原来是面对学生,现在是面对屏幕</h4>
<p>这里最大的变化就是:</p>
<ol>
<li><strong>互动变了</strong></li>
<li><strong>注意力管理变难了</strong></li>
<li><strong>获取反馈的渠道变了</strong></li>
</ol>
<p>先说第一点,互动变了。</p>
<p>现场培训是多地多形式互动,比如你可以在投影屏前讲课,可以让学生上台展示,你可以在白板前画画,可以让学生在白板上分享,可以与学生问答互动,等等。</p>
<p>但是线上培训却只剩一块屏幕了。没有了多地互动,也没有了多形式。这让互动变难了。</p>
<p>所以,这里给线上培训的讲师的建议是,把原来的靠肢体语音和场景变换的互动,改为语言互动,这就要求我们的讲师要像相声演员一样,能灵活应对各类问答。</p>
<p>接着说第二点,注意力管理变难了。</p>
<p>现场培训,讲师可以通过自己的培训技巧,很容易的管理学员的注意力。但现在你面对的是一块屏幕,很难去做这件事情。同时,由于屏幕的单一性,以及电脑的便利性,很容易导致学员走神,比如学员盯着屏幕久了就犯困了,或者屏幕弹了一条微信消息出来,学员就回消息去了。</p>
<p>那么这里给线上培训师的建议就是:</p>
<ol>
<li>强制要求每个学员打开摄像头。通过他们的表情,你可以做一定的注意力管理。</li>
<li>加强自己的语言能力。把自己变成一个像相声演员一样说很多话都不枯燥的讲师。</li>
<li>减少同一个画面出现的时间。比如一页PPT讲半小时,学员盯着这样的屏幕会怀疑是不是网络卡了。吸引注意力的其中一个办法就是制造变化,所以我们应该让屏幕有节奏的变化起来。</li>
</ol>
<p>最后说第三点,获取反馈的渠道变了。</p>
<p>现场培训的时候,讲师获取反馈的渠道通常有以下几个。</p>
<p>从学员的表情获取,如果学员皱眉头了,可能表示他没听懂。</p>
<p>从学员的动作获取,如果学员开小差了,你可以立刻发现。</p>
<p>线上培训则阻断了这样的获取反馈的渠道。</p>
<p>因此,这里给线上培训师的建议是:</p>
<ol>
<li>强制要求学员打开摄像头,这样你可以从表情获取一定的反馈。</li>
<li>建立获取反馈的规则,比如大家如果没有问题就在聊天室里面扣1。比如zoom支持让学员在讲师分享的屏幕里面,使用一些箭头符号等等,这样可以知道学生是在屏幕的哪个位置的知识点有疑惑。</li>
</ol>
<h4>4.学生的关注点变了</h4>
<p>为什么说学生的关注点变了呢,因为原来学生在现场是关注老师的一言一行,现在变成了鼠标的一举一动。关注点的转变,带来的则是教学形式的转变,以及教学技巧的转变。</p>
<p>讲师无法通过自己的举动来传递信息,因此线上培训传递信息的渠道变成了以下两个:</p>
<ol>
<li>屏幕</li>
<li>讲师的言语</li>
</ol>
<p>知道了传递信息的渠道,就需要讲师作出改变了。</p>
<p>首先需要<strong>把屏幕能传递的信息做到聚焦化和精准化</strong>。因为屏幕能出现的信息量是很大的,包括鼠标的移动也是一种信息,<strong>通常鼠标移动到的位置就是正在讲的重要知识点</strong>,因此讲师使用鼠标要慎重。</p>
<p>另外,很多讲师喜欢在屏幕上放很多内容,当你没有鼠标指引的时候,走神的学生就很难分辨你此刻在讲解的内容是什么。因此,屏幕出现的内容尽量精炼为好。</p>
<p>最后,更多的信息是靠讲师的言语传递的,屏幕上的内容也是靠讲师的言语去引导的。所以,线上培训更考验讲师的语言能力。</p>
<h4>5.一对一的对话变成了广播</h4>
<p>原来的现场培训,当学员在练习环节有问题,学员会向讲师提问,这种提问通常是一对一的,然后讲师会进行一对一的指导。而线上培训则不一样了,学员只能通过视频提问,因此,一对一的对话则变成了广播式的,所有人都能听到你们的对话。</p>
<p>这样的广播是有好处和坏处的。</p>
<ul>
<li>好处是,通常问题都是有代表性的,进行广播可以同时指导有同样问题的学员。也可以巩固其他学员在这部分的知识。</li>
<li>但也有坏处,坏处就是广播式会导致部分内向的学员不敢提问了,也同时会让那些专心练习的人分心。</li>
</ul>
<p>这里就需要讲师主动在练习环节去识别那些有问题的学员,同时给出因人而异的高效的指导。</p>
<h2>结尾</h2>
<p>分享了这么多,后面我们还会继续尝试各种各样的线上培训形式,继续总结分享。</p>
<p>希望我们的服务不只能现场提供,也能在线上提供相等质量的服务。</p>
<p>也希望有线上经验的小伙伴多多交流,总结方法论。把零散的经验变成可复用的资产。</p>
哪些小技巧可以提升一场培训的质感
https://segmentfault.com/a/1190000021270200
2019-12-12T16:29:05+08:00
2019-12-12T16:29:05+08:00
Woody
https://segmentfault.com/u/woodyyan
0
<h3>前言</h3>
<p>一场专业的培训需要关注很多方面才能呈现一场有质感的培训,这些东西包括但不限于:</p>
<ul>
<li>节奏</li>
<li>气场</li>
<li>注意力管理</li>
<li>氛围管理</li>
<li>临场应变能力</li>
<li>呈现设计</li>
<li>互动设计</li>
<li>课程设计</li>
<li>引导设计</li>
<li>翻转设计</li>
<li>形象设计</li>
</ul>
<p>我们今天不说这些抽象的理论,我们今天就说一些能落地的小技巧,帮助你提升一场培训的质感。</p>
<h3>培训前</h3>
<ol>
<li>
<p>提前踩点</p>
<ol>
<li>熟悉现场,规划现场。</li>
<li>准备好培训必须的物料。</li>
</ol>
</li>
<li>
<p>了解学员</p>
<ol>
<li>通过提前获得的学员资料了解</li>
<li>提前到现场与学员认识</li>
<li>
<p>需要了解的维度包括但不限于:</p>
<ol>
<li>技术背景</li>
<li>组成关系</li>
<li>工作方式</li>
</ol>
</li>
</ol>
</li>
<li>
<p>如果学员认知中的概念和我们的概念有偏差时,除非是培训内容,否则不去做纠正。而是去做一个映射,用学员的语言去交流。</p>
<ol><li>比如学员认知中理解的团队是50人左右的account,而我们理解的团队是5-8人的一个组,那当我们在培训时说每个团队要进行code review的时候,学员会非常困惑50个人如何一起code review。</li></ol>
</li>
</ol>
<h3>关于紧张</h3>
<p><strong>上台前:</strong></p>
<ol>
<li>
<p>降低期望值</p>
<ol>
<li>看轻结果。看轻结果不是意味着降低质量,而是一种心理暗示。</li>
<li>允许出丑。先出丑,再出众,再出名。</li>
<li>允许紧张。心理学家研究发现:一旦允许紧张出现之后,演讲人就会变得很真实,他就会展现最真实的自我,观众也更容易接受他。</li>
</ol>
</li>
<li>
<p>增加把握度</p>
<ol>
<li>不断实践</li>
<li>充分准备</li>
</ol>
</li>
<li>到现场提前认识一些朋友,或者给朋友打电话聊一聊接下来要讲的内容。和熟悉的人谈论内容,可以让自己不那么紧张,至少可以先正常的谈论有关话题。</li>
</ol>
<p><strong>上台后:</strong></p>
<ol>
<li>
<p>把听众想象成冬瓜</p>
<ol><li>这样的好处,你的内心里就不会把听众当成一种威胁,他们对你是无害的。</li></ol>
</li>
<li>目光看稍远处,不直视观众。或者是看那些比较友善的听众,告诉自己他们是站在你这边的。</li>
<li>紧张的时候喝口水,用这个时间想词。</li>
<li>带一个自己熟悉的道具,这些你熟悉的道具会给自己安全感。</li>
</ol>
<h3>开场</h3>
<ol>
<li>
<p>很多人在开场的自我介绍不知道说什么,大部分人就介绍了我是谁就结束了。自我介绍有很多炫酷的方式,这里给大家介绍一个最基本的模版就是:</p>
<ol><li>我是谁,我从哪里来,我是做什么的,我今天要做什么,为什么是我做。</li></ol>
</li>
<li>在开始一个话题前,先获得学员输入,收集期望和问题,带着学员的期望和问题去讲课。让学员在一开始就先思考今天要培训的内容,也让你的培训更契合学员的期望。</li>
<li>如果是多天的培训,第二天的培训开始的时候,记得带学员一起回顾一下前一天的内容。上下午的培训也需要简单回顾一下。增强培训之间的连贯性。</li>
</ol>
<h3>培训中</h3>
<ol>
<li>
<p>每个晦涩难懂的概念,尽量做到以下几点来帮助学生理解。</p>
<ol>
<li>列举一些有趣的例子</li>
<li>构建一个清晰的场景</li>
<li>说一个故事</li>
<li>画图解释</li>
</ol>
</li>
<li>讲完一个概念,随时在心里反问自己讲清楚没。同时从学员的表情中去获取反馈。</li>
<li>不停回顾总结每个小环节,帮学员加强记忆。并在开始下一个环节前向学员确认没有任何疑问。</li>
<li>大家在互动讨论的时候,讲师要下去寻访每个组的状态,倾听每个组的讨论。发现他们讨论中的问题,总结他们讨论的内容,发散自己讲课的思路,这些都会成为下一个讲课环节的素材。</li>
<li>两个讲师之间的交接,上一个讲师可以简单介绍下一个讲师以及要讲的内容,下一个讲师可以谢谢一下上一位讲师,再自我介绍。</li>
<li>当想要让学员做什么的时候,不要站在自己的角度去下命令,而要站在学员的角度去讲解,不然学员不明白要做什么。</li>
<li>尽量不要在PPT上放很复杂的图表或者很长一段话。如果放了,就要在PPT上highlight出重点。或者要么就指着PPT一点点讲解,要么就让学员先看后讲或者先讲后看。不然学员要么听你讲没有看到PPT,要么看PPT错过了你的讲解。</li>
<li>每个讨论、动手和分享环节都需要做时间控制。</li>
<li>每次尽量让不同的学员来做分享、互动和答题,增强每个人的参与感。</li>
<li>每个人或者小组分享完了之后,问一下其他人或者小组有没有什么问题,增加每个组之间的互动与参与感。保证均等的参与。</li>
<li>声音要洪亮,吐字要清晰。声音小显得在自言自语,不自信。吐字不清晰,学员就听不清楚,容易走神。</li>
<li>如果动手环节发现大部分人问题比较多,可以先暂停一下,统一再阐述一般概念,统一收集一下问题,集中解答一下,然后再继续。</li>
<li>PPT中如果有英文,最好解释一下中文意思,如果学员不理解这个英文意思,很可能在后面的内容中走神。</li>
<li>埋下伏笔或者设计连环问题。这类设计能让整个培训在呈现上提升一个档次。</li>
<li>中途休息时,不要忘了去和学员聊天,去了解学员,了解他们的技术背景、组成关系、工作习惯等,这些信息都能帮助你改善后续的培训内容。</li>
<li>讲解一些常见问题或者场景的时候,可以这样说:“实际工作中大家问得比较多的一个问题是...“,或者:”通常在大部分项目上,这类问题都是...“。类似于这样的说辞会显得你很有经验,增强学员对你的信任感,塑造你的权威形象。</li>
</ol>
<h3>提问相关</h3>
<ol>
<li>当学员回答了一个问题,或者做了一个分享。一定记得肯定、总结并升华学员的内容。一个比较常见的套路是:这位同学回答(分享)得非常好,他既提到了XXX,又说到了XXX,这些内容不仅能让我们XXX,又能让我们XXX。</li>
<li>
<p>当讲师向学生提问时,不仅要引导学生回答what,还需要引导学生思考更深层次的东西。引导式提问的模版是:</p>
<ol>
<li>What?回答what,让学员认知清楚</li>
<li>So what?回答what背后的意义,升华这个问题</li>
<li>Now what?回答怎么做才能达到这个意义,促使学生行动</li>
</ol>
</li>
<li>
<p>当学生向讲师提问时,回答的套路是:肯定,复述,解答,确认</p>
<ol>
<li>这是一个很有思考的问题。(肯定学员,增加学员的提问的信心。)</li>
<li>你的问题是不是...(复述问题,确保你没理解错,也让其他学员能听清楚问题是什么)</li>
<li>针对这个问题,答案是...(有条理的解答问题)</li>
<li>我是否回答了你的问题?(确认是否解答了疑惑,让学员知道你是非常关注他的问题的。)</li>
</ol>
</li>
<li>
<p>如何提高学员回答问题的积极性?</p>
<ol>
<li>如果你希望学生举手回答,那么你也要举起手来以身作则。</li>
<li>先问封闭式问题,让学员能简单快速的回答。比如问是否,或者给选择。</li>
<li>回答积极性很好了,再问开放式问题。</li>
<li>最重要的是当学员回答完了之后,要使用提问相关里面的第一条tip。</li>
</ol>
</li>
</ol>
<h3>培训冷知识</h3>
<ol>
<li>培训中的时间密码90/20/8,90是指每90分钟要休息一次,20是指每20分钟要转换一种学习方式,8是指每8分钟要调动学员一次。</li>
<li>讲师的N种死法:读PPT,批评学员,严重拖堂,离题万里,自我陶醉,逻辑不清,只看屏幕,端和装。</li>
</ol>
<h3>结语</h3>
<p>小技巧太多了,总结不完了。下次我就直接开始总结大技巧了,告诉大家哪些大技巧可以提升一场培训的质感。</p>
记一次Spring Batch完整入门实践
https://segmentfault.com/a/1190000016278038
2018-09-05T14:07:06+08:00
2018-09-05T14:07:06+08:00
Woody
https://segmentfault.com/u/woodyyan
32
<h2>前言</h2>
<p>本文将从0到1讲解一个Spring Batch是如何搭建并运行起来的。<br>本教程将讲解从一个文本文件读取数据,然后写入MySQL。</p>
<h2>什么是 Spring Batch</h2>
<p>Spring Batch 作为 Spring 的子项目,是一款基于 Spring 的企业批处理框架。通过它可以构建出健壮的企业批处理应用。Spring Batch 不仅提供了统一的读写接口、丰富的任务处理方式、灵活的事务管理及并发处理,同时还支持日志、监控、任务重启与跳过等特性,大大简化了批处理应用开发,将开发人员从复杂的任务配置管理过程中解放出来,使他们可以更多地去关注核心的业务处理过程。</p>
<p>更多的介绍可以参考官网:<a href="https://link.segmentfault.com/?enc=o0jQ4DkL6NR5s%2FVIkhC5iw%3D%3D.42R28FexCsgylYyDZJpRyPHD8Gbtp%2B2tcwaseLCi99DA71M9z9eukaO%2BHSN6DBzp" rel="nofollow">https://spring.io/projects/sp...</a></p>
<h2>环境搭建</h2>
<p>我是用的Intellij Idea,用gradle构建。</p>
<p>可以使用Spring Initializr 来创建Spring boot应用。地址:<a href="https://link.segmentfault.com/?enc=RHBBicrmaK9z%2FEN98v%2BgNQ%3D%3D.icQetUqKcthWcWjnXMBJ%2FWS7Qf21h8NIsGZY8dXipYM%3D" rel="nofollow">https://start.spring.io/</a></p>
<p><img src="/img/bVbgsGL?w=1920&h=716" alt="图片描述" title="图片描述"></p>
<p>首先选择Gradle Project,然后选择Java。填上你的Group和Artifact名字。</p>
<p>最后再搜索你需要用的包,比如Batch是一定要的。另外,由于我写的Batch项目是使用JPA向MySQL插入数据,所以也添加了JPA和MySQL。其他可以根据自己需要添加。</p>
<p>点击Generate Project,一个项目就创建好了。</p>
<p>Build.gralde文件大概就长这个样子:</p>
<pre><code>buildscript {
ext {
springBootVersion = '2.0.4.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
group = 'com.demo'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
compile('org.springframework.boot:spring-boot-starter-batch')
compile('org.springframework.boot:spring-boot-starter-jdbc')
compile("org.springframework.boot:spring-boot-starter-data-jpa")
compile group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-joda', version: '2.9.4'
compile group: 'org.jadira.usertype', name: 'usertype.core', version: '6.0.1.GA'
compile group: 'mysql', name: 'mysql-connector-java', version: '6.0.6',
testCompile('org.springframework.boot:spring-boot-starter-test')
testCompile('org.springframework.batch:spring-batch-test')
}
</code></pre>
<h2>Spring Batch 结构</h2>
<p>网上有很多Spring Batch结构和原理的讲解,我就不详细阐述了,我这里只讲一下Spring Batch的一个基本层级结构。</p>
<p>首先,Spring Batch运行的基本单位是一个Job,一个Job就做一件批处理的事情。<br>一个Job包含很多Step,step就是每个job要执行的单个步骤。</p>
<p>如下图所示,Step里面,会有Tasklet,Tasklet是一个任务单元,它是属于可以重复利用的东西。<br>然后是Chunk,chunk就是数据块,你需要定义多大的数据量是一个chunk。</p>
<p>Chunk里面就是不断循环的一个流程,读数据,处理数据,然后写数据。Spring Batch会不断的循环这个流程,直到批处理数据完成。</p>
<p><img src="/img/bVbgsIW?w=560&h=497" alt="图片描述" title="图片描述"></p>
<h2>构建Spring Batch</h2>
<p>首先,我们需要一个全局的Configuration来配置所有的Job和一些全局配置。</p>
<p>代码如下:</p>
<pre><code>@Configuration
@EnableAutoConfiguration
@EnableBatchProcessing(modular = true)
public class SpringBatchConfiguration {
@Bean
public ApplicationContextFactory firstJobContext() {
return new GenericApplicationContextFactory(FirstJobConfiguration.class);
}
@Bean
public ApplicationContextFactory secondJobContext() {
return new GenericApplicationContextFactory(SecondJobConfiguration.class);
}
}
</code></pre>
<p>@EnableBatchProcessing是打开Batch。如果要实现多Job的情况,需要把EnableBatchProcessing注解的modular设置为true,让每个Job使用自己的ApplicationConext。</p>
<p>比如上面代码的就创建了两个Job。</p>
<h2>例子背景</h2>
<p>本博客的例子是迁移数据,数据源是一个文本文件,数据量是上百万条,一行就是一条数据。然后我们通过Spring Batch帮我们把文本文件的数据全部迁移到MySQL数据库对应的表里面。</p>
<p>假设我们迁移的数据是Message,那么我们就需要提前创建一个叫Message的和数据库映射的数据类。</p>
<pre><code>@Entity
@Table(name = "message")
public class Message {
@Id
@Column(name = "object_id", nullable = false)
private String objectId;
@Column(name = "content")
private String content;
@Column(name = "last_modified_time")
private LocalDateTime lastModifiedTime;
@Column(name = "created_time")
private LocalDateTime createdTime;
}
</code></pre>
<h2>构建Job</h2>
<p>首先我们需要一个关于这个Job的Configuration,它将在SpringBatchConfigration里面被加载。</p>
<pre><code>@Configuration
@EnableAutoConfiguration
@EnableBatchProcessing(modular = true)
public class SpringBatchConfiguration {
@Bean
public ApplicationContextFactory messageMigrationJobContext() {
return new GenericApplicationContextFactory(MessageMigrationJobConfiguration.class);
}
}
</code></pre>
<p>下面的关于构建Job的代码都将写在这个MessageMigrationJobConfiguration里面。</p>
<pre><code>public class MessageMigrationJobConfiguration {
}
</code></pre>
<p>我们先定义一个Job的Bean。</p>
<pre><code>@Autowired
private JobBuilderFactory jobBuilderFactory;
@Bean
public Job messageMigrationJob(@Qualifier("messageMigrationStep") Step messageMigrationStep) {
return jobBuilderFactory.get("messageMigrationJob")
.start(messageMigrationStep)
.build();
}
</code></pre>
<p>jobBuilderFactory是注入进来的,get里面的就是job的名字。<br>这个job只有一个step。</p>
<h3>Step</h3>
<p>接下来就是创建Step。</p>
<pre><code>@Autowired
private StepBuilderFactory stepBuilderFactory;
@Bean
public Step messageMigrationStep(@Qualifier("jsonMessageReader") FlatFileItemReader<Message> jsonMessageReader,
@Qualifier("messageItemWriter") JpaItemWriter<Message> messageItemWriter,
@Qualifier("errorWriter") Writer errorWriter) {
return stepBuilderFactory.get("messageMigrationStep")
.<Message, Message>chunk(CHUNK_SIZE)
.reader(jsonMessageReader).faultTolerant().skip(JsonParseException.class).skipLimit(SKIP_LIMIT)
.listener(new MessageItemReadListener(errorWriter))
.writer(messageItemWriter).faultTolerant().skip(Exception.class).skipLimit(SKIP_LIMIT)
.listener(new MessageWriteListener())
.build();
}
</code></pre>
<p>stepBuilderFactory是注入进来的,然后get里面是Step的名字。<br>我们的Step中可以构建很多东西,比如reader,processer,writer,listener等等。</p>
<p>下面我们就逐个来看看step里面的这些东西是如何使用的。</p>
<h3>Chunk</h3>
<p>Spring batch在配置Step时采用的是基于Chunk的机制,即每次读取一条数据,再处理一条数据,累积到一定数量后再一次性交给writer进行写入操作。这样可以最大化的优化写入效率,整个事务也是基于Chunk来进行。</p>
<p>比如我们定义chunk size是50,那就意味着,spring batch处理了50条数据后,再统一向数据库写入。<br>这里有个很重要的点,chunk前面需要定义数据输入类型和输出类型,由于我们输入是Message,输出也是Message,所以两个都直接写Message了。<br>如果不定义这个类型,会报错。</p>
<pre><code>.<Message, Message>chunk(CHUNK_SIZE)
</code></pre>
<h3>Reader</h3>
<p>Reader顾名思义就是从数据源读取数据。<br>Spring Batch给我们提供了很多好用实用的reader,基本能满足我们所有需求。比如FlatFileItemReader,JdbcCursorItemReader,JpaPagingItemReader等。也可以自己实现Reader。</p>
<p>本例子里面,数据源是文本文件,所以我们就使用FlatFileItemReader。FlatFileItemReader是从文件里面一行一行的读取数据。<br>首先需要设置文件路径,也就是设置resource。<br>因为我们需要把一行文本映射为Message类,所以我们需要自己设置并实现LineMapper。</p>
<pre><code>@Bean
public FlatFileItemReader<Message> jsonMessageReader() {
FlatFileItemReader<Message> reader = new FlatFileItemReader<>();
reader.setResource(new FileSystemResource(new File(MESSAGE_FILE)));
reader.setLineMapper(new MessageLineMapper());
return reader;
}
</code></pre>
<h3>Line Mapper</h3>
<p>LineMapper的输入就是获取一行文本,和行号,然后转换成Message。<br>在本例子里面,一行文本就是一个json对象,所以我们使用JsonParser来转换成Message。</p>
<pre><code>public class MessageLineMapper implements LineMapper<Message> {
private MappingJsonFactory factory = new MappingJsonFactory();
@Override
public Message mapLine(String line, int lineNumber) throws Exception {
JsonParser parser = factory.createParser(line);
Map<String, Object> map = (Map) parser.readValueAs(Map.class);
Message message = new Message();
... // 转换逻辑
return message;
}
}
</code></pre>
<h3>Processor</h3>
<p>由于本例子里面,数据是一行文本,通过reader变成Message的类,然后writer直接把Message写入MySQL。所以我们的例子里面就不需要Processor,关于如何写Processor其实和reader/writer是一样的道理。<br>从它的接口可以看出,需要定义输入和输出的类型,把输入I通过某些逻辑处理之后,返回输出O。</p>
<pre><code>public interface ItemProcessor<I, O> {
O process(I item) throws Exception;
}
</code></pre>
<h3>Writer</h3>
<p>Writer顾名思义就是把数据写入到目标数据源里面。<br>Spring Batch同样给我们提供很多好用实用的writer。比如JpaItemWriter,FlatFileItemWriter,HibernateItemWriter,JdbcBatchItemWriter等。同样也可以自定义。</p>
<p>本例子里面,使用的是JpaItemWriter,可以直接把Message对象写到数据库里面。但是需要设置一个EntityManagerFactory,可以注入进来。</p>
<pre><code>@Autowired
private EntityManagerFactory entityManager;
@Bean
public JpaItemWriter<Message> messageItemWriter() {
JpaItemWriter<Message> writer = new JpaItemWriter<>();
writer.setEntityManagerFactory(entityManager);
return writer;
}
</code></pre>
<p>另外,你需要配置数据库的连接等东西。由于我使用的spring,所以直接在Application.properties里面配置如下:</p>
<pre><code>spring.datasource.url=jdbc:mysql://database
spring.datasource.username=username
spring.datasource.password=password
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect
spring.jpa.show-sql=true
spring.jpa.properties.jadira.usertype.autoRegisterUserTypes=true
spring.jackson.serialization.write-dates-as-timestamps=false
spring.batch.initialize-schema=ALWAYS
spring.jpa.hibernate.ddl-auto=update
</code></pre>
<p>spring.datasource相关的设置都是在配置数据库的连接。<br>spring.batch.initialize-schema=always表示让spring batch在数据库里面创建默认的数据表。<br>spring.jpa.show-sql=true表示在控制台输出hibernate读写数据库时候的SQL。<br>spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect是在指定MySQL的方言。</p>
<h3>Listener</h3>
<p>Spring Batch同样实现了非常完善全面的listener,listener很好理解,就是用来监听每个步骤的结果。比如可以有监听step的,有监听job的,有监听reader的,有监听writer的。没有你找不到的listener,只有你想不到的listener。</p>
<p>在本例子里面,我只关心,read的时候有没有出错,和write的时候有没有出错,所以,我只实现了ReadListener和WriteListener。<br>在read出错的时候,把错误结果写入一个单独的error列表文件中。</p>
<pre><code>public class MessageItemReadListener implements ItemReadListener<Message> {
private Writer errorWriter;
public MessageItemReadListener(Writer errorWriter) {
this.errorWriter = errorWriter;
}
@Override
public void beforeRead() {
}
@Override
public void afterRead(Message item) {
}
@Override
public void onReadError(Exception ex) {
errorWriter.write(format("%s%n", ex.getMessage()));
}
}
</code></pre>
<p>在write出错的时候,也做同样的事情,把出错的原因写入单独的日志中。</p>
<pre><code>public class MessageWriteListener implements ItemWriteListener<Message> {
@Autowired
private Writer errorWriter;
@Override
public void beforeWrite(List<? extends Message> items) {
}
@Override
public void afterWrite(List<? extends Message> items) {
}
@Override
public void onWriteError(Exception exception, List<? extends Message> items) {
errorWriter.write(format("%s%n", exception.getMessage()));
for (Message message : items) {
errorWriter.write(format("Failed writing message id: %s", message.getObjectId()));
}
}
}
</code></pre>
<p>前面有说chuck机制,所以write的listener传入参数是一个List,因为它是累积到一定的数量才一起写入。</p>
<h3>Skip</h3>
<p>Spring Batch提供了skip的机制,也就是说,如果出错了,可以跳过。如果你不设置skip,那么一条数据出错了,整个job都会挂掉。<br>设置skip的时候一定要设置什么Exception才需要跳过,并且跳过多少条数据。如果失败的数据超过你设置的skip limit,那么job就会失败。<br>你可以分别给reader和writer等设置skip机制。</p>
<pre><code>writer(messageItemWriter).faultTolerant().skip(Exception.class).skipLimit(SKIP_LIMIT)
</code></pre>
<h3>Retry</h3>
<p>这个和Skip是一样的原理,就是失败之后可以重试,你同样需要设置重试的次数。<br>同样可以分别给reader,writer等设置retry机制。</p>
<p>如果同时设置了retry和skip,会先重试所有次数,然后再开始skip。比如retry是10次,skip是20,会先重试10次之后,再开始算第一次skip。</p>
<h2>运行Job</h2>
<p>所有东西都准备好以后,就是如何运行了。<br>运行就是在main方法里面用JobLauncher去运行你制定的job。</p>
<p>下面是我写的main方法,main方法的第一个参数是job的名字,这样我们就可以通过不同的job名字跑不同的job了。</p>
<p>首先我们通过运行起来的Spring application得到jobRegistry,然后通过job的名字找到对应的job。</p>
<p>接着,我们就可以用jobLauncher去运行这个job了,运行的时候会传一些参数,比如你job里面需要的文件路径或者文件日期等,就可以通过这个jobParameters传进去。如果没有参数,可以默认传当前时间进去。</p>
<pre><code>public static void main(String[] args) {
String jobName = args[0];
try {
ConfigurableApplicationContext context = SpringApplication.run(ZuociBatchApplication.class, args);
JobRegistry jobRegistry = context.getBean(JobRegistry.class);
Job job = jobRegistry.getJob(jobName);
JobLauncher jobLauncher = context.getBean(JobLauncher.class);
JobExecution jobExecution = jobLauncher.run(job, createJobParams());
if (!jobExecution.getExitStatus().equals(ExitStatus.COMPLETED)) {
throw new RuntimeException(format("%s Job execution failed.", jobName));
}
} catch (Exception e) {
throw new RuntimeException(format("%s Job execution failed.", jobName));
}
}
private static JobParameters createJobParams() {
return new JobParametersBuilder().addDate("date", new Date()).toJobParameters();
}
</code></pre>
<p>最后,把jar包编译出来,在命令行执行下面的命令,就可以运行你的Spring Batch了。</p>
<pre><code>java -jar YOUR_BATCH_NAME.jar YOUR_JOB_NAME
</code></pre>
<h2>调试</h2>
<p>调试主要依靠控制台输出的log,可以在application.properties里面设置log输出的级别,比如你希望输出INFO信息还是DEBUG信息。<br>基本上,通过查看log都能定位到问题。</p>
<pre><code>logging.path=build/logs
logging.file=${logging.path}/batch.log
logging.level.com.easystudio=INFO
logging.level.root=INFO
log4j.logger.org.springframework.jdbc=INFO
log4j.logger.org.springframework.batch=INFO
logging.level.org.hibernate.SQL=INFO
</code></pre>
<h2>Spring Batch数据表</h2>
<p>如果你的batch最终会写入数据库,那么Spring Batch会默认在你的数据库里面创建一些batch相关的表,来记录所有job/step运行的状态和结果。</p>
<p>大部分表你都不需要关心,你只需要关心几张表。<br><img src="/img/bVbgsMk?w=426&h=392" alt="图片描述" title="图片描述"></p>
<p>batch_job_instance:这张表能看到每次运行的job名字。<br><img src="/img/bVbgsOH?w=1344&h=184" alt="图片描述" title="图片描述"></p>
<p>batch_job_execution:这张表能看到每次运行job的开始时间,结束时间,状态,以及失败后的错误消息是什么。<br><img src="/img/bVbgsOM?w=1728&h=316" alt="图片描述" title="图片描述"></p>
<p>batch_step_execution:这张表你能看到更多关于step的详细信息。比如step的开始时间,结束时间,提交次数,读写次数,状态,以及失败后的错误信息等。<br><img src="/img/bVbgsOT?w=476&h=988" alt="图片描述" title="图片描述"></p>
<h2>总结</h2>
<p>Spring Batch为我们提供了非常实用的功能,对批处理场景进行了完善的抽象,它不仅能实现小数据的迁移,也能应对大企业的大数据实践应用。它让我们开发批处理应用可以事半功倍。</p>
<p>最后一个tips,搭建Spring Batch的过程中,会遇到各种各样的问题。<strong>只要善用Google,都能找到答案。</strong></p>
一个AR Tech Radar的诞生
https://segmentfault.com/a/1190000016184588
2018-08-28T21:12:26+08:00
2018-08-28T21:12:26+08:00
Woody
https://segmentfault.com/u/woodyyan
1
<h2>什么是AR Tech Radar</h2>
<p>技术雷达是ThoughtWorks每年出品两期的技术趋势报告,一般来说大家看到的雷达都是文档形式,其中有一张技术全景图,以及每个技术点的成熟度分析。而AR技术雷达就是在原始文档的基础上,利用AR技术将其立体化呈现,并在其中添加互动元素。 <br><img src="/img/bVbf4uE?w=1128&h=657" alt="图片描述" title="图片描述"><br><img src="/img/bVbf4uJ?w=767&h=1250" alt="图片描述" title="图片描述"></p>
<h2>为什么要做AR Tech Radar</h2>
<ol>
<li>技术雷达一直以来都是文档的形式呈现,如果能通过包含在内的最新技术呈现出来,岂不是更能体现技术雷达的意义。同时也能增加技术雷达的交互和科技感。</li>
<li>XR Community作为AR/VR等技术的探索者,AR技术雷达是我们社区内部产品的第一步尝试。</li>
<li>我们也不知道为什么,就是想做AR Tech Radar。</li>
</ol>
<h2>AR Tech Radar的技术选型</h2>
<p>目前市面上能做AR的技术有很多,基本上每家大公司都有自己的AR技术。为什么我们会选择ARKit呢?(ARKit是苹果做AR软件开发的一个工具,使开发者能为iOS设备开发增强现实应用。)</p>
<p>之所以选择ARKit一个很重要的原因就是懒,只想选一个学习成本比较低的技术。</p>
<p>其实AR技术强依赖于承载它的硬件,所以选择AR技术其实就是在选择硬件平台。我们期望能使用一个广泛的平台,让AR技术雷达被更多的人接触到。目前AR硬件平台使用最广泛,也最容易让用户接触到的就是iOS,所以我们选择了ARKit。</p>
<p>其中还有一些其他的人气技术,比如:</p>
<ul>
<li>ARCore,它是Google推出的运行在Android上的技术,但目前只有几款顶配的Android手机可以运行。</li>
<li>Hololens,它是微软的AR眼镜,购买成本较高,很难被普通用户接触到。</li>
<li>Unity,它支持iOS和Android跨平台。</li>
</ul>
<p>那为什么我们没有选择在unity上进行AR开发,让它同时支持iOS和android呢?一个原因是ARKit和ARCore是才出来的新技术,它在unity上的兼容性和使用上肯定有很多未知的坑,我们期望使用比较稳定的平台。另外一个原因是,我们期望尝试用原生开发,以便更深刻的体验AR开发的过程。今后我们会尝试使用例如unity等工具进行开发,然后和原生开发做一个对比。</p>
<h2>如何开发AR Tech Radar</h2>
<p><strong>准备</strong></p>
<p>ARKit是苹果的技术,语言首选是Swift。 硬件需要支持ARKit的一台Mac和一部iOS设备。因为ARKit不支持模拟器运行,所以必须使用真机进行全程的开发调试。 开发软件是Xcode。</p>
<p><strong>前期构想</strong></p>
<p>做AR开发需要有两部分准备,一部分是本身的编程,另外一部分就是3D建模和空间相关的知识。编程不必多说,只要会Swift就能开始。3D建模不是我们的长项,所以前期我们做了很多调查,比如自己使用3D建模软件做一个雷达模型,或者去购买别人做好的雷达模型,或者外包给第三方公司做一个3D模型,再或者找会3D建模的同学加入我们。</p>
<p>但这些方案都被我们否决了,原因有很多,比如我们的经费有限,不能支持我们去找外包,也没有现成的模型给我们购买。而自己去学习3D建模的学习时间也长,同时也没找到会3D建模的同学。</p>
<p>因此我们决定用ARKit支持的形状来组合一个雷达。</p>
<p>我们曾经设想过很多次AR技术雷达应该长什么样。</p>
<p>比如罗马斗兽场的样子,让技术每层递进。 <br><img src="/img/bVbf4u2?w=860&h=644" alt="图片描述" title="图片描述"></p>
<p>或者是一个圆球,人站在球里面,被周围的技术包围,大概像这样: <br><img src="/img/bVbf4u8?w=704&h=465" alt="图片描述" title="图片描述"></p>
<p>再或者,它应该是一个立在你面前的展台,技术雷达就摆在用户面前,大概像这样: <br><img src="/img/bVbf4vc?w=775&h=438" alt="图片描述" title="图片描述"></p>
<p>最终这些想法都被我们暂时搁置了,最主要的原因是我们没有能力和人手去实现那些炫酷的样子,并且我们觉得技术雷达就应该用它最朴素的样子展示给大家,应该被大家关注的是技术雷达的内容,而不是这个3D物体。所以最终我们决定用一个圆饼来展示技术雷达。</p>
<p><strong>开发</strong></p>
<p>首先,3D建模不是我们的长项,所以我们选用了ARKit支持的基本形状来组合出一个技术雷达的大饼。因此,我们使用了一个圆柱体和三个圆管,如下图。正中间是一个圆柱,用三个圆管把圆柱包围起来,就形成了雷达圆饼。 <br><img src="/img/bVbf4vf?w=941&h=946" alt="图片描述" title="图片描述"></p>
<p>接着,为了让整个雷达看起来更立体,我们使用了圆球来作为每个技术的标示点,同时让标题浮在圆球的正上方。如下图。 <br><img src="/img/bVbf4vi?w=941&h=621" alt="图片描述" title="图片描述"></p>
<p>我才不会告诉你,每个技术标示点在第一版的设计中是圆锥形的,看起来像雷达上的一坨坨屎。请看下图。 <br><img src="/img/bVbf4vj?w=941&h=677" alt="图片描述" title="图片描述"></p>
<p>然后就是添加交互,让用户在点击某一个圆球的时候弹出它的具体阐述。就像下图一样。我们在圆球的正上方弹出一个半透明白板,并把标题和内容放在上面。 白板上的字不同于圆球上的标题,它是印在平面上的,而不像标题是3D立体的。因为大段的文字不适合全部做成3D立体的字,这对资源的消耗和3D的计算是很大的。所以我们利用3D纹理贴图,把文字描述贴到了白板上。 <br><img src="/img/bVbf4vl?w=923&h=1250" alt="图片描述" title="图片描述"></p>
<p><strong>数据</strong></p>
<p>最后就是如何添加数据,我们希望这个AR技术雷达能运用到每一年的技术雷达,这就要求我们添加进去的数据是支持更新的。</p>
<p>所以我们使用了一个单独的文件来存储每一期的所有技术,文件内容包含了所有技术相关的信息,比如名字、详细介绍、它所处的象限、它的分类等等。</p>
<p>这样的好处就是下一次的雷达技术出来之后,我们只需要更新这个独立的文件就可以看到最新的AR技术雷达了。</p>
<h2>3D开发过程中遇到的困难与趣事</h2>
<p>遇到的第一个奇葩事件就是,第一次我们添加了一个物体,可是在摄像头里面怎么都找不到,后来我们无意中把镜头对着天空突然发现那个物体在空中飘着。原因就是ARKit世界里面的尺寸是和现实世界一样的,单位是米,而我们的离地高度设的是3米,因此它就跑到空中去了。</p>
<p>另一个和这个是相似的,我们加了一个圆管放在地上,可是在地上怎么也找不到那个圆管。后来我们才发现,我们的圆管的尺寸太大了,把我们全部包在圆管里面了。</p>
<p>第三个有意思的事情是,我们添加了一个平面,上面写了一些东西,可是我们在镜头里面却怎么也找不到这个平面。通过各种debug和调查研究,才发现,我们在平面的背面,原来对于没有厚度的平面,只能在正面才能看得见。</p>
<p>还有一个比较棘手的问题就是,比如有些物体需要旋转两个90度再加上一些变换才能达到我们想要的位置。这对空间想象能力的要求就比较高,我们尝试了很多种旋转和变换,才最终找到了想要的位置。</p>
<h2>未来的发展</h2>
<p>我们期望AR技术雷达能发展成为每次技术雷达发布的官方AR应用,通过不同的途径和不同的体验让更多的人了解技术雷达,让人们能和技术雷达有一些有意义的互动。</p>
<p>所以未来我们期望能不断完善AR技术雷达,让它成为一个炫酷的、交互式很强的应用。</p>
<p>打开脑洞想象一下,通过使用AR技术雷达,你不仅可以看到每次更新的新技术、还能够通过一些交互直观的看到它的历史轨迹、应用场景以及具体实践,是不是一件很酷的事情?</p>