Quantcast
Channel: IT社区推荐资讯 - ITIndex.net
Viewing all 11848 articles
Browse latest View live

Apache Atlas元数据血缘关系(Lineage)功能研究_tomalun的专栏-CSDN博客

$
0
0

一、生成血缘数据

血缘关系数据通过Process生成,可以在数据导入时自动生成或通过RestAPI新增Process生成。

1sqoop同步自动生成血缘数据

sqoop同步MySQL数据库数据到hive,同步成功后,通过sqoop的 Atlas Hook自动生成血缘数据。

sqoop将MySQL数据库所有表数据同步到hive仓库命令:

sqoop import-all-tables  --connect jdbc:mysql://192.168.1.1:3306/testdb --username root --password ****** --hive-import --hive-database testdb  --m 1

Atlas管理台可以查看到每张表的血缘关系图:

 

2RestAPI接口生成血缘数据

通过Atlas的RestAPI接口新增Process,可以生成血缘数据。

例如将Atlas元数据管理的MySQL数据库表和hive数据表关联生成血缘数据,先查到两张表的guid值,然后构造请求数据调用接口: http://{atlas_host}:21000/api/atlas/v2/entity/bulk

请求消息:

{"entities":[{"typeName":" Process","attributes":{"owner":"root","createTime":"2020-05-07T10:32:21.0Z","updateTime":"","qualifiedName":"people@process@mysql://192.168.1.1:3306","name":"peopleProcess","description":"people Process","comment":"test people Process","contact_info":"jdbc","type":"table"," inputs":[{"guid": " 5a676b74-e058-4e81-bcf8-42d73f4c1729","typeName": "rdbms_table"}]," outputs":[{"guid": " 2e7c70e1-5a8a-4430-859f-c46d267e33fd","typeName": "hive_table"}]}}]}

Atlas管理台可以查看到表的血缘关系图:

3hive建表语句自动生成血缘数据

hive执行hive SQL语句create table t2 as select id, name from T1创建表,会自动生成表的血缘数据以及字段级的血缘数据。

Hive 2.2.0以下的低版本存在bug,字段级的血缘数据不能自动生成,需升级hive版本到2.2.0及以上才能正常生成字段级的血缘数据。

Atlas管理台可以查看到表的血缘关系图:

字段(列)级血缘图:

4、多个 Process联结的血缘图

 

 

二、管理血缘数据

1Rest API查询血缘数据

get请求:http://{atlas_host}:21000/api/atlas/v2/lineage/01d12e5f-1ef5-46a8-ac13-29be71e8f78e

响应消息:

{"baseEntityGuid":"01d12e5f-1ef5-46a8-ac13-29be71e8f78e","lineageDirection":"BOTH","lineageDepth":3,"guidEntityMap":{"5a676b74-e058-4e81-bcf8-42d73f4c1729":{"typeName":"rdbms_table","attributes":{"owner":"root","createTime":1577687198000,"qualifiedName":"testdb.p_people@mysql://192.168.1.1:3306","name":"p_people","description":"MySQL数据库表:testdb.p_people"},"guid":"5a676b74-e058-4e81-bcf8-42d73f4c1729","status":"ACTIVE","displayText":"p_people","classificationNames":[],"meaningNames":[],"meanings":[]},"2e7c70e1-5a8a-4430-859f-c46d267e33fd":{"typeName":"hive_table","attributes":{"owner":"hdfs","createTime":1578981817000,"qualifiedName":"testdb.p_people@primary","name":"p_people"},"guid":"2e7c70e1-5a8a-4430-859f-c46d267e33fd","status":"ACTIVE","displayText":"p_people","classificationNames":["people"],"meaningNames":[],"meanings":[]},"2b65eb7f-596e-48f0-a94d-240e56a4da93":{"typeName":"Process","attributes":{"owner":"root","qualifiedName":"people@process@mysql://192.168.1.1:3306","name":"peopleProcess","description":"people Process"},"guid":"2b65eb7f-596e-48f0-a94d-240e56a4da93","status":"ACTIVE","displayText":"peopleProcess","classificationNames":[],"meaningNames":[],"meanings":[]},"01d12e5f-1ef5-46a8-ac13-29be71e8f78e":{"typeName":"hive_process","attributes":{"qualifiedName":"testdb.p_people_tmp2@primary:1588921268000","name":"create table p_people_tmp2 as select peopleid,peopletype,credentialtype,credentialno,peoplename,gender,nation from p_people"},"guid":"01d12e5f-1ef5-46a8-ac13-29be71e8f78e","status":"ACTIVE","displayText":"create table p_people_tmp2 as select peopleid,peopletype,credentialtype,credentialno,peoplename,gender,nation from p_people","classificationNames":["people"],"meaningNames":[],"meanings":[]},"a4ccceb2-a52c-46a2-b4fd-27d26b8aad3f":{"typeName":"hive_table","attributes":{"owner":"hive","createTime":1588921268000,"qualifiedName":"testdb.p_people_tmp2@primary","name":"p_people_tmp2"},"guid":"a4ccceb2-a52c-46a2-b4fd-27d26b8aad3f","status":"ACTIVE","displayText":"p_people_tmp2","classificationNames":["people"],"meaningNames":[],"meanings":[]}},"relations":[{"fromEntityId":"01d12e5f-1ef5-46a8-ac13-29be71e8f78e","toEntityId":"a4ccceb2-a52c-46a2-b4fd-27d26b8aad3f","relationshipId":"148cc83d-5b67-4174-91e4-767509483e13"},{"fromEntityId":"2e7c70e1-5a8a-4430-859f-c46d267e33fd","toEntityId":"01d12e5f-1ef5-46a8-ac13-29be71e8f78e","relationshipId":"eb768346-d32a-40f9-bf04-d23abbcc3221"},{"fromEntityId":"2b65eb7f-596e-48f0-a94d-240e56a4da93","toEntityId":"2e7c70e1-5a8a-4430-859f-c46d267e33fd","relationshipId":"bea47efd-2645-4d8a-ba6b-8f4ef9bb7316"},{"fromEntityId":"5a676b74-e058-4e81-bcf8-42d73f4c1729","toEntityId":"2b65eb7f-596e-48f0-a94d-240e56a4da93","relationshipId":"517db5b7-f537-4e66-97f1-33c2863fb440"}]}

2、管理界面查看血缘图

可以在Atlas管理台每个实体详情的Lineage选项卡页面查看血缘图:

界面上有几个功能按钮可以操作,依次是重排血缘图、导出png图片、设置hover事件显示当前路径或节点详情、隐藏过滤、节点搜索、放大、缩小、全屏:


Hive 元数据表结构详解_豪猪的博客-CSDN博客_hive元数据表结构

$
0
0

原文地址: https://mp.weixin.qq.com/s?__biz=MzA3ODUxMzQxMA==&mid=2663993556&idx=1&sn=0e5291bd63426d747f32a7fd05128caa&scene=21#wechat_redirect


元数据是基础,这篇文章值得一读。

本文介绍Hive元数据库中一些重要的表结构及用途,方便Impala、SparkSQL、Hive等组件访问元数据库的理解。

1、存储Hive版本的元数据表(VERSION)

该表比较简单,但很重要。

VER_ID

SCHEMA_VERSION

VERSION_COMMENT

ID主键

Hive版本

版本说明

1

1.1.0

Set  by MetaStore

如果该表出现问题,根本进入不了Hive-Cli。比如该表不存在,当启动Hive-Cli时候,就会报错”Table ‘hive.version’ doesn’t exist”。

2、Hive数据库相关的元数据表(DBS、DATABASE_PARAMS)

DBS:该表存储Hive中所有数据库的基本信息,字段如下:

表字段

说明

示例数据

DB_ID

数据库ID

1

DESC

数据库描述

Default  Hive database

DB_LOCATION_URI

数据HDFS路径

hdfs://193.168.1.75:9000/test-warehouse

NAME

数据库名

default

OWNER_NAME

数据库所有者用户名

public

OWNER_TYPE

所有者角色

ROLE

 

DATABASE_PARAMS:该表存储数据库的相关参数,在CREATE DATABASE时候用WITH DBPROPERTIES(property_name=property_value, …)指定的参数。

表字段

说明

示例数据

DB_ID

数据库ID

1

PARAM_KEY

参数名

createdby

PARAM_VALUE

参数值

root

DBS和DATABASE_PARAMS这两张表通过DB_ID字段关联。

3、Hive表和视图相关的元数据表

主要有TBLS、TABLE_PARAMS、TBL_PRIVS,这三张表通过TBL_ID关联。

TBLS:该表中存储Hive表,视图,索引表的基本信息

表字段

说明

示例数据

TBL_ID

表ID

21

CREATE_TIME

创建时间

1447675704

DB_ID

数据库ID

1

LAST_ACCESS_TIME

上次访问时间

1447675704

OWNER

所有者

root

RETENTION

保留字段

0

SD_ID

序列化配置信息

41,对应SDS表中的SD_ID

TBL_NAME

表名

ex_detail_ufdr_30streaming

TBL_TYPE

表类型

EXTERNAL_TABLE

VIEW_EXPANDED_TEXT

视图的详细HQL语句


VIEW_ORIGINAL_TEXT

视图的原始HQL语句


 

TABLE_PARAMS:该表存储表/视图的属性信息

表字段

说明

示例数据

TBL_ID

表ID

1

PARAM_KEY

属性名

totalSize,numRows,EXTERNAL

PARAM_VALUE

属性值

970107336、21231028、TRUE

 

TBL_PRIVS:该表存储表/视图的授权信息

表字段

说明

示例数据

TBL_GRANT_ID

授权ID

1

CREATE_TIME

授权时间

1436320455

GRANT_OPTION


0

GRANTOR

授权执行用户

root

GRANTOR_TYPE

授权者类型

USER

PRINCIPAL_NAME

被授权用户

username

PRINCIPAL_TYPE

被授权用户类型

USER

TBL_PRIV

权限

Select、Alter

TBL_ID

表ID

21,对应TBLS表的TBL_ID

4、Hive文件存储信息相关的元数据表

主要涉及SDS、SD_PARAMS、SERDES、SERDE_PARAMS,由于HDFS支持的文件格式很多,而建Hive表时候也可以指定各种文件格式,Hive在将HQL解析成MapReduce时候,需要知道去哪里,使用哪种格式去读写HDFS文件,而这些信息就保存在这几张表中。

SDS:

该表保存文件存储的基本信息,如INPUT_FORMAT、OUTPUT_FORMAT、是否压缩等。TBLS表中的SD_ID与该表关联,可以获取Hive表的存储信息。

表字段

说明

示例数据

SD_ID

存储信息ID

41

CD_ID

字段信息ID

21,对应CDS表

INPUT_FORMAT

文件输入格式

org.apache.hadoop.mapred.TextInputFormat

IS_COMPRESSED

是否压缩

0

IS_STOREDASSUBDIRECTORIES

是否以子目录存储

0

LOCATION

HDFS路径

hdfs://193.168.1.75:9000/detail_ufdr_streaming_test

NUM_BUCKETS

分桶数量

0

OUTPUT_FORMAT

文件输出格式

org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat

SERDE_ID

序列化类ID

41,对应SERDES表

SD_PARAMS: 该表存储Hive存储的属性信息,在创建表时候使用STORED BY ‘storage.handler.class.name’ [WITH SERDEPROPERTIES (…)指定。

表字段

说明

示例数据

SD_ID

存储配置ID

41

PARAM_KEY

存储属性名


PARAM_VALUE

存储属性值


SERDES:该表存储序列化使用的类信息

表字段

说明

示例数据

SERDE_ID

序列化类配置ID

41

NAME

序列化类别名

NULL

SLIB

序列化类

org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe

 

SERDE_PARAMS:该表存储序列化的一些属性、格式信息,比如:行、列分隔符

表字段

说明

示例数据

SERDE_ID

序列化类配置ID

41

PARAM_KEY

属性名

field.delim

PARAM_VALUE

属性值

|

5、Hive表字段相关的元数据表

主要涉及COLUMNS_V2

COLUMNS_V2:该表存储表对应的字段信息

表字段

说明

示例数据

CD_ID

字段信息ID

21

COMMENT

字段注释

NULL

COLUMN_NAME

字段名

air_port_duration

TYPE_NAME

字段类型

bigint

INTEGER_IDX

字段顺序

119

6、Hive表分分区相关的元数据表

主要涉及PARTITIONS、PARTITION_KEYS、PARTITION_KEY_VALS、PARTITION_PARAMS

PARTITIONS:该表存储表分区的基本信息

表字段

说明

示例数据

PART_ID

分区ID

21

CREATE_TIME

分区创建时间

1450861405

LAST_ACCESS_TIME

最后一次访问时间

0

PART_NAME

分区名

hour=15/last_msisdn=0

SD_ID

分区存储ID

43

TBL_ID

表ID

22

LINK_TARGET_ID


NULL

 

PARTITION_KEYS:该表存储分区的字段信息

表字段

说明

示例数据

TBL_ID

表ID

22

PKEY_COMMENT

分区字段说明

NULL

PKEY_NAME

分区字段名

hour

PKEY_TYPE

分区字段类型

int

INTEGER_IDX

分区字段顺序

0

 

PARTITION_KEY_VALS:该表存储分区字段值

表字段

说明

示例数据

PART_ID

分区ID

21

PART_KEY_VAL

分区字段值

0  

INTEGER_IDX

分区字段值顺序

1

 

PARTITION_PARAMS:该表存储分区的属性信息

表字段

说明

示例数据

PART_ID

分区ID

21

PARAM_KEY

分区属性名

numFiles,numRows

PARAM_VALUE

分区属性值

1,502195

6、其他不常用的元数据表

DB_PRIVS

数据库权限信息表。通过GRANT语句对数据库授权后,将会在这里存储。

IDXS

索引表,存储Hive索引相关的元数据

INDEX_PARAMS

索引相关的属性信息

TBL_COL_STATS

表字段的统计信息。使用ANALYZE语句对表字段分析后记录在这里

TBL_COL_PRIVS

表字段的授权信息

PART_PRIVS

分区的授权信息

PART_COL_PRIVS

分区字段的权限信息

PART_COL_STATS

分区字段的统计信息

FUNCS

用户注册的函数信息

FUNC_RU

用户注册函数的资源信息


架构师图谱(上)

$
0
0

概述

“架构师图谱”是一个很宏大的命题,特别是优秀的架构师自身也是“由点到面再到图”,一点点成长积累起来,尝试写这篇文章的目的更多的是结合自身的一些架构、研发、管理经验对现阶段做一个复盘总结,所以这里更偏向于后端图谱,依赖于开源技术、云原生或者其他第三方服务。 这里会重点介绍一些技术栈、设计理念以及适应场景,这些可以作为我们选型时的依据。所谓“架构即决策”,是在一个有约束的盒子中寻求最优解。这个有约束的盒子是团队经验、成本、资源、进度、业务所处阶段等编织、掺杂在一起的综合体。本质上无优劣,但是存在恰当的架构用在合适的软件系统中,而这些就是决策的结果。

序章

一个技术图谱:
1.png

计划会分上、中、下三个篇章来介绍:
  • 上篇:重点聚焦在微服务和常用的消息队列,包括相关的选型以及一些理论基础
  • 中篇:主要集中在数据库、分布式(一致性/锁/缓存/发号/任务调度等),以及流媒体
  • 下篇:分享一些DevOps、项目管理、团队建设方向的一些经验


完整的思维导图:
2.png

微服务

微服务(英语:Microservices)是一种 软件架构风格,它是以专注于单一责任与功能的小型功能区块(Small Building Blocks)为基础,利用模块化的方式组合出复杂的大型应用程序,各功能区块使用与语言无关(Language-Independent/Language agnostic)的 API集相互通信。

微服务架构有别于更为传统的单体服务,可将应用拆分成多个核心功能。每个功能都被称为一项服务,可以单独构建和部署。这也体现了可扩展的基本思想:将原本大一统的系统拆成多个小部分,扩展时只修改其中一部分,通过这种方式减少改动范围,降低改动风险。 微服务架构涵盖了服务的多个方面,包括网关、通信协议、服务注册/发现、可观察性、如何合理的划分等等。

理论基础

微服务的理论基础主要用来指导微服务架构设计、服务拆分,确定合适的服务粒度和边界。在做微服务之前我们首先要想明白我们现有系统面临什么样的问题,为什么需要微服务,随后才是怎么做。微服务很多核心理念其实在半个世纪前的一篇文章中就被阐述过了,而且这篇文章中的很多论点在软件开发飞速发展的这半个世纪中竟然一再被验证,这就是康威定律(Conway’s Law)。在康威的这篇文章中,最有名的一句话就是:


Organizations which design systems are constrained to produce designs which are copies of the communication structures of these organizations. – Melvin Conway(1967)
中文直译大概的意思就是:设计系统的组织,其产生的设计等同于组织之内、组织之间的沟通结构。 最初这篇文章只是描述作者自己的发现和总结,后来“人月神话”中,引用这个观点,并将其“吹捧”成现在熟知的“高位定律”,其中的一些核心观点可以概括如下:
  • 组织沟通方式决定系统设计,对于复杂的系统,聊设计就离不开聊人与人的沟通,解决好人与人的沟通问题,才能有一个好的系统设计
  • 时间再多一件事情也不可能做的完美,但总有时间做完一件事情,这与架构设计的“简单、合适、演化”思维不谋而合
  • 线型系统和线型组织架构间有潜在的异质同态特征,更直白的说,你想要什么样的系统,就搭建什么样的团队,定义好系统的边界和接口,团队内应该是自治的,这样将沟通成本维持在系统内部,每个子系统就会更加内聚
  • 大的系统组织总是比小系统更倾向于分解,面对复杂的系统及组织,往往可以采用分而治之


但是当我们的业务和组织架构复杂度比较高的时候,很多概念只从技术角度很难去抽象,这就需要我们自上而下,建立起通用语言,让业务人员和研发人员说一样的话,把思考层次从代码细节拉到业务层面。越高层的抽象越稳定,越细节的东西越容易变化。通过对不同领域的建模,逐步确定领域范围和业务边界,这也就是领域驱动设计(DDD)。 DDD是一种在面向高度复杂的软件系统时,关于如何去建模的方法论,它的关键点是根据系统的复杂程度建立合适的模型,DDD中的界限上下文也完美匹配了微服务的高内聚、低耦合特性,这也为我们微服务的划分提供了强有力的基础。DDD实施的一般步骤是:
  • 根据需求划分出初步的领域和限界上下文,以及上下文之间的关系
  • 进一步分析每个上下文内部,识别出哪些是实体,哪些是值对象
  • 对实体、值对象进行关联和聚合,划分出聚合的范畴和聚合根
  • 为聚合根设计仓储,并思考实体或值对象的创建方式
  • 在工程中实践领域模型,并在实践中检验模型的合理性,倒推模型中不足的地方并重构


但是DDD也不是银弹,特别是在一些新业务场景,本身就充满了很多的不确定性,一次性把边界划清楚并不是一件很容易的事。大家在一个进程里,调整起来会相对容易,然后让不同的界限上下文各自演化,等到了一定程度之后再考虑微服务也是一个不错的选择。

网关

作为微服务的统一入口,也肩负着整个微服务的流量接入、管理、聚合、安全等,从服务分层的角度可以划分为接入网关和业务网关。 接入网关接入网关提供最基础的流量接入和安全防护能力,侧重于全局,与业务无关。
  • 域名&DNS,作为服务的流量入口,对外通过域名和DNS提供服务,国内域名厂商一般都依托于共有云或被共有云厂商收购,用来完善自由的云生态,像阿里的万网,腾讯的DNSPod等,也有国外的AWS,GoDaddy和Namecheap等,可以用作.me等国内无法托管或备案域名的管理,其次也可以借助DNS(HTTPDNS、EDNS)实现跨地域、运营商网络等负载均衡,实现异地多活、就近访问、容灾等。

  • 负载均衡(LB),主要负责请求的转发代理,按机器负载来分配流量等,对外提供VIP,这里的负载可以宽泛的理解为系统的压力,可以用CPU负载来衡量,也可以用连接数、I/O使用率、网卡吞吐量等来衡量。负载均衡器按服务层级来划分,除了前边提到的DNS,还有集群级别的硬件负载均衡,以及机器级别的软件负载均衡。
    • DNS/硬件负载均衡(F5/A10)主要用来应对海量用户的访问,中小量用户使用无疑会增加更多的维护和采购成本。
    • 软件负载均衡可以选择自研或上云,LVS、Keepalived主要用于四层(IP+端口)的负载均衡,在四层的基础之上如果要实现应用层(域名/URL/用户会话)等的7层负载均衡,可以使用Nginx、Keepalived的组合。

  • 除此之外,网关也负责服务整体的安全防护,SSL,IPV6等。
    • 安全防护目的是保护服务数据以及可用性,例如防范常见的DDOS/CC网络攻击,反爬虫,自定义访问控制,自研成本往往比较高,可以借助云上一系列的高防、防火墙服务。
    • SSL(TLS)用来提供外部https访问,https可以防止数据在传输过程中不被窃取、改变,确保数据的完整性,在支付或者用户登录等敏感数据场景,可以起到一定的保护作用,同时https页面对搜索引擎也比较友好。
    • IPV6,全球43亿IPV4地址已经在2019年年底耗尽,网信办在2018年开始就已经推行各大运营商、CDN厂商、互联网核心产品支持IPV6,我们公司之前也是试点之一。IPV6的支持只需要增加一条“AAAA”DNS记录,将域名解析到自持IPV6的IP/VIP即可。IPV4到IPV6由于存在兼容性等问题,一定是长期共存的,过渡方案可以采用IPV6代理(IPV6代理转发到IPV4服务)或者双栈(同时支持IPV6和IPV4)。


业务网关:
3.png

业务网关作为业务的最上层出口,一般承担起业务接入或者BFF的工作,例如基础的路由、鉴权、限流、熔断降级、服务聚合、插件化能力,并可以通过可视化界面管理网关配置。可选框架有基于OpenResty的Kong、APISIX以及其他语言相关的Spring Cloud Gateway、gRPC-Gateway等等,国内开源的Goku、Kratos、go-zero go框架,有很多比较有意思的组件实现,我们日常业务上也可以借鉴。
  • 鉴权,鉴权的目的是为了验证用户、请求等的有效性,例如用户身份鉴权(JWT/Oauth2/Cookie),请求鉴权(请求签名、请求加密),鉴权逻辑也花样繁多,大多需要基于业务定制化,通过网关插件能很好的集成进来。
  • 限流,限流是为了做一定的流量控制,防止对系统产生过大压力从而影响整个服务。可以基于单台机器或整个集群限流,常见的方式有限制总量和限制速率,超过的则排队或丢弃,例如令牌桶(弹性)/漏桶(匀速)算法。
  • 熔断降级,熔断作为服务断路器,当下游的服务因为某种原因突然变得不可用或响应过慢(这里既可以指单次请求也可以指一段时间),上游服务为了保证自己整体服务的可用性,不再继续调用目标服务,直接返回,这样也能对整体链路起到保护作用。如果目标服务情况好转则恢复调用,同时结合降级策略提升服务的鲁棒性。常见的有Hystrix/Resilience4J(Hystrix虽然已停止更新,但现有功能已经能满足大多业务场景)。
  • 重试,大量网络IO,避免不了会出现因网络抖动,出现连接失败或者超时,重试可以提高请求的最终成功率,削平服务毛刺。但重试也有可能放大故障,所以可以结合退避策略(backoff)、限制单点重试、限制链路重试这些策略进行优雅的重试,同时也可以采用更加激进的“对冲请求”提前(tp99时间未响应时)发起重试请求,降低系统时延。
  • 插件化,各个网关集成插件的方式尽不相同,但是目的都是为了集成技术人员编写的一些业务相关的通用能力,例如前边提到的身份鉴权、请求鉴权等等。另外作为业务网关插件,也可以编写一些基础业务(API鉴权、请求格式化)逻辑,直接透传请求到服务层,省去很多BFF和上下游对接的工作。
  • BFF,Backend For Frontend,可以按照业务逻辑,以串行、并行和分支等结构编排多个服务API,为服务提供聚合、适配、裁剪(只返回需要的字段)功能,核心是API的动态编排以满足日益增长的业务逻辑,降低前端与微服务之间的对接成本。BFF并不意味着只能由后端实现,也可以在前端通过GraphQL等API查询语言实现。


协议

服务间的通信方式是在采用微服务架构时需要做出一个最基本的决策,统一的协议标准也能大大降低服务的联调和维护成本。
  • HTTP REST,REST更确切的讲是指的API设计风格,而不是协议标准。通常基于使用HTTP,URL,和JSON这些现有的广泛流行的协议和标准。符合REST设计风格的API称作RESTful API。在实际应用中大多实现的是伪REST API,例如用POST请求同时实现资源的增删改,或者为了请求的扩展性,资源的增删改查都使用POST JSON。
  • RPC,RPC协议描绘了客户端与服务端之间的点对点调用流程,包括stub、通信、RPC消息协议部分。可以基于TCP,也可以基于http。在实际应用中,还需要考虑服务的高可用、负载均衡等问题,所以产品级的RPC框架除了点对点的RPC协议的具体实现外,还应包括服务的发现与注册、提供服务的多台Server的负载均衡、服务的高可用等更多的功能。目前的RPC框架大致有两种不同的侧重方向,一种偏重于服务治理(Dubbo、Motan),另一种偏重于跨语言调用(Thrift/GRPC)。


RPC vs HTTP REST优点:
  • 更清晰的API定义,例如gRPC协议的定义文件proto,自身就可以作为很好的API文档,日常开发中也可以把proto文件独立版本库管理,精简目录结构,方便不同的服务引用。
  • 更好的传输效率,通过序列化和反序列化进一步压缩网络传输数据,不过序列化、反序列化也会有一定的性能损耗,protobuf可以说很好的兼顾了这两点。
  • 更合适的容错机制,可以基于实际的业务场景,实现更合适的超时控制与异常重试机制,以应对网络抖动等对服务造成的影响。


在一些特定场景,例如:OpenAPI、BFF等,HTTP REST可以更大程度上降低外部团队的接入成本。并且RPC也有调试不便、多语言互通需要对应的SDK支持这些问题,各有利弊。综合考虑来看,除了一些特定场景,如果我们已经有相对完善的基础设施支撑(RPC框架、服务治理),RPC可以为一个更合适的选择。

服务注册/发现

服务注册主要是通过将微服务的后端机器IP、端口、地域等信息注册起来,并结合一定的发现机制使客户端的请求能够直连具体的后端机器。从实现方式上可以分为服务端模式与客户端模式:
  • 服务端模式,也可以说是传统模式,通过借助负载均衡器和DNS实现,负载均衡器负责健康检查、负载均衡策略,DNS负责实现访问域名到负载均衡器IP/VIP的映射。通过直接暴露域名和端口的方式提供客户端访问。
  • 客户端模式,可以借助注册中心实现,注册中心负责服务的注册与健康检查,客户端通过监听配置变更的方式及时把配置中心维护的配置同步到本地,通过客户端负载均衡策略直接向后端机器发起请求。


从两种模式的实现方式上可以看出:
  • 服务端模式注册与发现都由服务端完成,这样可以使客户端专注在自身的业务实现,但是由于依赖负载均衡器,也就是集中式的proxy,proxy需要维护双向连接,也很容易使自己成为系统瓶颈,可用性的高低直接决定了服务质量,并且DNS缓存机制也会导致故障发生时,迁移并不能及时完成。当然在服务量少,且负载均衡器有VIP的情况下,我们也可以不使用DNS。
  • 客户端模式注册与发现由配置中心和客户端共同完成,通过分布式的方式,可以避免出现proxy节点性能瓶颈问题,但是可靠性与性能瓶颈很容器出现在配置中心上,并且客户端的也需要一定的接入成本。好在开源的已经有很成熟的架构方案与丰富的客户端SDK,例如etcd/ZooKeeper/Consul,Consul提供开箱即用的功能,etcd社区和接入易用性方面更优一些,他们之间的一些具体区别:

    4.png


配置中心

配置中心从使用场景来讲,一类是前边讲到的服务注册、发现和KV存储,例如etcd/ZooKeeper/Consul,在Kubernetes场景下也可以通过ConfigMap/Secret将配置写入本地文件、环境变量或者共享的Volume中,这样没有了中心服务的依赖和客户端的接入,可以实现一些老旧服务的无侵入式改造。但是作为配置中心,除了基础的配置数据,一些情况下还要开放给非开发人员(测试、运维、产品)使用,完善的控制台、权限管理、Dashbord的支持,也非常重要,这类可以参考Nacos(阿里开源)/Apollo(携程开源)。Nacos在读写性能上优于Apollo,但是功能特性(例如权限管理)稍逊于Apollo。

可观察性

在控制论中,可观察性是用系统输出到外部的信息来推断系统内部运运行状态的一种度量方式

在云原生时代,容器和服务的生命周期是紧密联系在一起的,相较在传统的单体服务运行在物理主机或者虚拟机当中,排查问题的时候显得非常不便,这种复杂性导致了一个定义研发运营效率的MTTR(平均故障修复时间)指标急剧增加。所以这里更强调的是微服务的可观察性,需要提前想好我们要如何观察容器内的服务以及服务之间的拓扑信息、各式指标的搜集等,这些监测能力相当重要。 可观察性三大支柱围绕Tracing(链路追踪)、Logging(日志)和Metrics(度量)展开,这三个维度几乎涵盖了应用程序的各种表征行为,开发人员通过收集并查看这三个维度的数据时刻掌握应用程序的运行情况。很长一段时间,这三者是独立存在的,随着时间的推移,这三者已经相互关联,相辅相成。
5.jpeg

链路追踪

链路追踪为分布式应用的开发者提供了完整的调用链路还原、调用请求量统计、链路拓扑、应用依赖分析等工具,可以帮助开发者快速分析和诊断分布式应用架构下的性能瓶颈,提高微服务时代下的开发诊断效率以及系统的可观察性。 为了解决不同的分布式系统API不兼容的问题,诞生了OpenTracing规范,OpenTracing中的Trace可以被认为是由多个Spacn组成的DAG图。
[Span A]  ←←←(the root span)  
        |
 +------+------+
 |             |
[Span B]      [Span C] ←←←(Span C 是 Span A 的孩子节点, ChildOf)
 |             |
[Span D]      +---+-------+
           |           |
       [Span E]    [Span F] >>> [Span G] >>> [Span H]
                                   ↑
                                   ↑
                                   ↑
                     (Span G在Span F后被调用,FollowsFrom)

OpenTracing专注在tracing,除此之外还有包含了Metrics的OpenCensus标准,以及由CNCF推出,融合OpenTracing和OpenCensus的OpenTelemetry。OpenTelemetry旨在实现云原生时代可观察性指标(Tracing、Logging、Metrics)的统一收集和处理,同时提供推动这些标准实施的组件和工具。 OpenTracing中的佼佼者当属Jaeger、Zipkin、Skywalking。他们之间的一些对比:
6.png

Zipkin开源时间长,社区相对丰富,Jaeger更加轻量,也是Istio推荐方案,SkyWalking支持部分语言(Java、PHP、Python等)的无侵入式接入。另外APM(应用性能)监控的支持也会影响到我们的选型。 除此之外,面对线上海量请求,如果采用抽样采样策略,那就需要支持一定的流量染色,把我们核心关注的请求(例如链路中发生了错误、部分请求耗时过高等)都进行采样,可以通过结合opentelemetry-collector以及开箱即用的 tailsamplingprocessor构建Pipeline插件实现。

日志

服务间的链路日志能否帮助我们判断错误发生的具体位置,这类业务日志主要集中在访问日志/打点日志等等。随着大数据的兴起,我们对数据的分析解读能力越来越强,日志作为原始数据则体现出了更大的价值,例如用户的行为分析,反垃圾,舆情分析等等。

业务日志:这类日志重点在于通过不同级别的日志,及时发现分析系统存在的异常,RFC 5424定义的8中日志级别:
  • Emergency:system is unusable
  • Alert:action must be taken immediately
  • Critical:critical conditions
  • Error:error conditions
  • Warning:warning conditions
  • Notice:normal but significant condition
  • Informational:informational messages
  • Debug:debug-level messages


在实际使用过程中可能会对日志级别进行简化和调整,一般来讲Warning及以上的日志是需要重点关注的,需要做好及时的监控告警,Warning以下的日志也可以辅助问题的定位。 日志写入可以选择写入消息队列,也可以选择落地磁盘,将关心的结构化或非结构化日志、业务模块信息(如果是细粒度的微服务,可以选择将日志放同一模块收集),以及级别、时间(who、when、where、how、what)等要素正确的写入正确写入后再收集到日志服务。写入消息队列需要考虑消息队列的选型以及做好可用性和积压监控,写入磁盘需要考虑写入性能以及日志的切割清理,例如Golang的zap+rotatelogs组合。日志收集的话,由于Logstash资源消耗相对比较大,虚拟机环境中可以使用Filebeat来替代,更严苛的线上或容器环境,可以使用Fluentd/Fluentd Bit。日志最终汇总到ES和Kibana做展示,通过Esalert定制告警策略。

大数据日志:大数据日志本质上也对应着我们一定的业务场景,但大多是海量日志、高吞吐量场景,所以对海量日志的收集和存储是较大的挑战。实现方案我们可以采用高吞吐量的流式中间件,例如Kafka/Plusar等,在结合流式处理(Flink)或者批处理(Spark)系统,将数据汇总到Hadoop进行分析,这里涉及到的中间件和数据库可参考后续章节。

指标

指标是有关系统的离散的数据点,这些指标通常表示为计数或度量,并且通常在一段时间内进行汇总或计算,一般用来做基础的资源监控和业务监控:
  • 资源监控:CPU、内存、IO、fd、GC等
  • 业务监控:QPS、模调、耗时分布等


Zabbix作为老牌的监控系统,适合更复杂的物理机、虚拟机、数据库等更复杂的场景,同时也拥有更丰富的图形化界面,但是Prometheus作为云原生的代表作,与Kubernetes、容器等能更好的结合,协同Grafana实现可定制化的界面,另外存储基于TSDB,相比于关系型数据库也有更好的扩展性。以Prometheus为例,支持的数据类型有:
  • Counter只增不减的计数器,例如请求数(http_requests_total)。基于此数据模型,使用Prometheus提供的强大PromQL表达式能够拓展出更加适合开发观察的指标数据。 分钟增量请求:increase(http_requests_total[1m]) 分钟QPS:rate(http_requests_total[1m])
  • Gauge可增可减的时刻量,例如Go语言协程数(go_goroutines) 波动量:delta(go_goroutines[10m])
  • Histogram直方图,不同区间内样本的个数。例如,耗时50ms-100ms每分钟请求量,100ms-150ms每分钟请求量。
  • Summary 概要,反应百分位值。例如,某RPC接口,95%的请求耗时低于150ms,99%的请求耗时低于200ms。


Service Mesh

Service Mesh这个服务网格专注于处理服务和服务之间的通信,包括我们前边讲的服务发现、熔断降级、安全、流量控制、可观察性等能力。这些通用能力在Service Mesh出现之前,由Lib/Framework完成,这样就可以在开发层面上很容易地集成到我们的应用服务中。但是并没有办法实现跨语言编程,有什么改动后,也需要重新编译重新发布服务。理论上应该有一个专门的层来干这事,于是出现了Sidecar,Sidecar集群就成了Service Mesh,加上对整个集群的管理控制面板,就成了现在的Service Mesh架构,可以说Service Mesh是云原生时代的必然产物。
7.jpeg

目前比较流行的Service Mesh开源软件是Istio和Linkerd,还有更加轻量级的Conduit,它们都可以在Kubernetes中集成。Istio基于Golang编写,使用Envoy作为Sidecar,在服务治理方面职责分明,国内落地案例相较Linkerd 、Conduit更加广泛。 由于Service Mesh承担了服务核心的流量调度环节,再给我们带来便利的同时,也引入很多的不可控因素,例如:Sidecar组件不可用,将直接导致系统出现致命问题。所以在充分做好服务可观察性的前提下,也要保证Service Mesh的高可用,一种比较好的方式是,除了在本机有 Sidecar,我们还可以部署一下稍微集中一点的 Sidecar——比如为某个服务集群部署一个集中式的 Sidecar。一旦本机的有问题,可以走集中的。

消息队列

在计算机科学中,消息队列(英语:Message queue)是一种进程间通信或同一进程的不同线程间的通信方式,软件的贮列用来处理一系列的输入,通常是来自用户。消息队列提供了异步的通信协议,每一个贮列中的纪录包含详细说明的数据,包含发生的时间,输入设备的种类,以及特定的输入参数,也就是说:消息的发送者和接收者不需要同时与消息队列交互。消息会保存在队列中,直到接收者取回它。
8.png

实际应用场景中,消息队列也经常作为中间件,用于异步解耦、削峰填谷、数据广播、错峰与流控、最终一致性等,在一些核心的大数据分析、交易支付等场景也经常扮演重要角色,消息队列的选型主要侧重以下几点:
  • HA:自身的高可用性保障,避免消息队列的引入而影响整体服务的可用性
  • 高吞吐:在面对海量数据写入能否保持一个相对稳定、高效的数据处理能力
  • 功能丰富性:是否支持延迟消息、事务消息、死信队列、优先级队列等
  • 消息广播:是否支持将消息广播给消费者组或者一组消费者
  • 消息堆积能力:在数据量过大时,是否允许一定消息堆积到broker
  • 数据持久性:数据持久化策略的采用,也决定着数据在宕机恢复后是否会丢失数据
  • 重复消费:是否支持ack机制,在消费者未正确处理消息时,支持重新消费
  • 消息顺序性:针对顺序消费的场景保证数据按写入时间的顺序性


这里着重对比一下Redis、RabbitMQ/RocketMQ、Kafka、Plusar。

Redis

Redis实现消息队列可以通过List类型、Pub/Sub、Stream(Redis 5.0)类型来实现,HA使用多副本或者集群的方式。作为消息队列使用起来非常方便,但是也有很多的弊端:
  • 功能丰富性:只支持普通的消息类型
  • 数据持久性:Pub/Sub只提供缓冲区广播能力,不进行持久化,List/Stream即使基于aof和rdb持久化策略,但是并没有事务性保障,在宕机恢复后还是存在丢失数据的可能性
  • 消息堆积能力:List随长度增大,内存不断增长;Pub/Sub只在缓冲区内堆积,缓冲区满消费者强制下线;Stream创建时可以指定队列最大长度,写满后剔除旧消息


除此之外,List类型无法支持消息广播,和Pub/Sub一样也不支持重复消费。结合整体来看Redis作为消息队列大多数只应用在数据量小,对丢失数据不敏感的业务场景,适用范围较小,复杂业务并且有一定运维支撑的情况下,可以直接考虑企业级消息中间件。

RabbitMQ vs Kafka vs RocketMQ

这几个可以作为企业级消息中间件的代表,RabbitMQ和kafka的一些详细对比,可以参考之前写的这篇文章《 消息队列RabbitMQ与Kafka对比分析》。而RocketMQ在设计之初就借鉴了很多RabbitMQ、Kafka的设计理念,例如:Routing、多副本、顺序写(IO),也广泛应用在淘宝双十一等场景。

HA

在HA方面他们都是通过副本的方式,区别是RabbitMQ是集群级别的副本,Kafka是多partiton和ISR、选举机制,而RocketMQ通过多(master/slave)副本同时保障NameServer和Broker。

高吞吐

Kafka和RocketMQ通过直接操作文件系统,相比于RabbitMQ,顺序写能大幅度提升数据的处理速度。Kafka为了进一步提升消息的吞吐量,可以采用客户端缓冲队列的方式批量发送,但也会存在宕机丢失数据的可能性,可以通过设置batch.size与linger.ms来动态调整,相比于RocketMQ更加灵活。Kafka的partition机制的确会带来性能的提升,但是在Topic不断增多的情况下,众多的partition及副本也将顺序写逐步退化为随机写,并且扩容时,由于hash值的变化,也会涉及到大量partiton数据的迁移。RocketMQ采用commitlog的方式实现全局写,所以能支持更多的Topic,扩容也不涉及大量数据的迁移。

功能丰富性

Kafka只有基础的消息类型,RabbitMQ支持优先级队列,通过TTL和死信队列可以实现消息的延迟和重试,但是需要提前创建好对应重试频率的队列,例如:1s重试队列,10s重试队列,RocketMQ则内置了18个重试频率“1s 5s 10s 30s 1m 2m……”,另外也具有独有的2PL事务消息,很好的保障业务逻辑与消息发送的一致性。

重复消费

他们三者都采用ACK机制保障了单条消息重复消费的能力,Kafka通过offset和partition特殊的ttl机制(segment过期,按文件名顺序清理),能支持通过重置offset来回溯历史数据。

消息顺序性

RabbitMQ和RocketMQ可以保证写入同一topic的顺序性,但是在多个消费者同时消费的情况下还是会出现乱序的情况,在数据量较大的时候,我们也可以通过单个消费者消费,再按照一定的分发策略分配给多个消费者执行,只不过会提升整体复杂度,同时会带来更多的HA、维护成本考量。Kafka可以保障单个partition的顺序性,并且每个partiton只允许一个消费者来消费(N:1),这就从策略上避免了多消费者的情况,在数据量较大的情况下,可以通过划分更多的partition提升数据处理能力。 综合来讲,RabbitMQ、RocketMQ使用Queue模型,丰富的消息队列功能,更多的应用在业务场景,Kafka基于Streaming模型,结合批处理、流式处理,更多的应用在大数据分析场景。

Pulsar

Pulsar作为Apache开源、云原生的消息中间件,诞生之初就引发了很大的关注。设计上避免了Kafka遇到的功能丰富性、扩容等方面的问题,采用计算、存储分离的架构,broker层只作为“API接口层”,存储交给更专业的bookeeper,由于broker层的无状态性,结合Kubernetes等非常方便的进行扩容。并且Pulsar支持多个消费模型提升消费者处理能力,例如:exclusive、failover、shared、key-shared等,可以说综合了Kafka和其他消息中间件的众多优点。
9.jpeg

  • HA、高吞吐:和Kafka类似,通过多partition和选举机制功,除此之外,还支持丰富的跨地域复制能力
  • 功能丰富性:可以支持秒级的延迟消息,以及独特的重试队列和私信队列
  • 消息顺序性:为了实现partition消息的顺序性,和Kafka一样,都需要将消息写入到同一broker,区别是Kafka会同时存储消息在该broker,broker和partiton绑定在一起,而Pulsar可以将消息分块(segment)后,更加均匀的分散到bookeeper节点上,broker只需要记录映射关系即可,这样在资源扩容时,可以更加快速便捷


像能量守恒定律一样,系统的复杂度往往也是守恒的,实现即高性能又高可用的消息中间件需要的技术复杂性,不会凭空消失,只会从一个地方转移到另一个地方,消息队列本质上可以理解为feature+fs,只不过存储、计算分离架构,将各层间的职责分离,使每一层都能专注在自身领域,以应对海量数据和更加复杂多变的环境,这也是现在新技术发展的一个趋势。 作为后起之秀,的确可以站在巨人的肩膀上,避免很多设计上的不足,同时引入一些新的架构理念,但是要成功的在其中分一杯羹,同样也要面临用户学习成本高、缺少杀手级应用、如何迁移等等这些现实性的问题,不过依靠良好的社区和技术先驱,随着时间的变迁,这些短板也会逐步补齐,真正适应当前时代的技术一定会脱颖而出。ps:腾讯云最近开源 Rop,支持RocketMQ相对平滑的迁移至Pulsar。

原文链接: https://blog.xstudio.mobi/a/230.html

    Istio究竟是干嘛的? - 知乎

    $
    0
    0

    当微服务架构体系越来越复杂的时候,需要将“业务服务”和“基础设施”解耦,将一个微服务进程一分为二:


    • 一个进程实现业务逻辑,biz,即上图白色方块
    • 一个进程实现底层技术体系,proxy,即上图蓝色方块,负载均衡、监控告警、服务发现与治理、调用链…等诸多基础设施,都放到这一层实现

    如此解耦之后:

    • biz不管是调用服务,还是提供服务,都只与本地的proxy进行本地通信
    • 所有跨网的通信,都通过proxy之间进行

    要聊ServiceMesh,就不得不提Istio,它是ServiceMesh目前最流行的实践,今天说说Istio是干啥的。

    画外音:不能落伍。

    什么是Istio?

    Istio是ServiceMesh的产品化落地,它的一些关键性描述是:

    (1) 帮助微服务之间建立连接,帮助研发团队更好的管理与监控微服务,并使得系统架构更加安全

    画外音:Istio helps you to connect, secure, control, and observe microservices.

    (2) 帮助微服务分层解耦,解耦后的proxy层能够更加专注于提供基础架构能力,例如:

    • 服务发现(discovery);
    • 负载均衡(load balancing);
    • 故障恢复(failure recovery);
    • 服务度量(metrics);
    • 服务监控(monitoring);
    • A/B测试(A/B testing);
    • 灰度发布(canary rollouts);
    • 限流限速(rate limiting);
    • 访问控制(access control);
    • 身份认证(end-to-end authentication);

    画外音:佩服,硬是凑齐了十条,其实ServiceMesh还能提供更多基础服务功能。

    (3) 使得业务工程团队与基础架构团队都更加高效的工作,各自专注于自己的工作,更好的彼此赋能

    画外音:说的还是解耦。

    Istio官网是怎么吹嘘自己的?

    画外音:这个问题的另一个问法是“为什么大家要来用Istio”。

    Istio非常牛逼,如果要实施ServiceMesh,必须用Istio,因为:

    (1) 可以通过,在现有服务器新增部署边车代理(sidecar proxy),应用程序不用改代码,或者只需要改很少的代码,就能实现上述N项基础功能

    画外音:你信了么?

    (2) 可以通过,控制后台,简单改改配置,点点按钮,就能管理和查看上述N项基础功能

    (3) 以下特性,Istio在这个环节里进行了附加说明:

    • 负载均衡支持多协议,HTTP, gRPC, WebSocket, TCP
    • 通过路由、重试、故障转移对流量进行细粒度流控
    • 通过可插拔策略层以及可配置API,能够支持流量访问控制、限速、配额管理
    • 自动度量、日志收集、调用跟踪
    • 服务到服务的身份认证

    Istio的核心特性是什么?

    Istio强调了它提供的五项关键特性:

    (1) 流控(traffic management)

    画外音:断路器(circuit breakers)、超时、重试、高可用、多路由规则、AB测试、灰度发布、按照百分比分配流量等。

    (2) 安全(security)

    画外音:加密、身份认证、服务到服务的权限控制、K8S里容器到容器的权限控制等。

    (3) 可观察(observability)

    画外音:追踪、监控、数据收集,通过控制后台全面了解上行下行流量,服务链路情况,服务运行情况,系统性能情况,国内微服务架构体系,这一块做得比较缺乏。

    (4) 平台无关系(platform support)

    画外音:K8s,物理机,自己的虚机都没问题。

    (5) 集成与定制(integration and customization)

    画外音:可定制化扩展功能。

    Istio的吹嘘与特性,对于国外很多通过RESTful提供内网服务的公司,很有吸引力,但相对于国内微服务架构,未必达到了很好的拉拢效果:

    • 国内基本都是TCP的RPC框架,多协议支持未必是必须的
    • RPC框架里,路由、重试、故障转移、负载均衡、高可用都是最基础的
    • 流控、限速、配额管理,是服务治理的内容,在微服务架构初期是锦上添花
    • 自动度量,系统入口出口数据收集,调用跟踪,可观察和可操控的后台确实是最吸引人的
    • 服务到服务的身份认证,微服务基本是内网访问,在架构初期也只是锦上添花;

    另外一个花边,为什么代理会叫sidecar proxy?


    看了上图就容易懂了,biz和proxy相生相伴,就像摩托车(motor)与旁边的车厢(sidecar)。未来,sidecar和proxy就指微服务进程解耦成两个进程之后,提供基础能力的那个代理进程


    Istio究竟是干嘛的?

    《麦田里的守望者》你读懂了吗? - 知乎

    $
    0
    0

    家有男孩,一定要读的书

    《麦田里的守望者》1951年第一次出版就引起轰动和争议,有人称赞这是一本关照青少年心灵成长的伟大小说,也有的人说这是一本引导青少年走向歧途的世俗小说,不管怎样,《麦田里的守望者》成为迄今为止再版次数最多的书。其作者杰罗姆.大卫.塞林格本人也充满神秘色彩。

    以学习成绩堪忧,屡次被学校开除,满口脏话16岁少年作为主人公的小说,为什么能够成为美国中学和高校必读的课外读物?为什么能够成为哈佛社会学课程《美国的社会结构和性质》必读书目?

    以下的内容也许能回答这些问题。

    《麦田里的守望者》讲诉了霍尔顿.考尔菲德圣诞节来临之前三天的零散活动,初读小说让人琢磨不透,不知作者想表达什么。但是这些零散的片断就像一颗颗散落的珍珠,当它们串联在一起就成为一条美丽的项链。

    故事线“内心充满挫败感的十六岁少年重获自我价值和快乐的心路历程”

    把所有的珍珠完美串联在一起。

    那接下来就让我们一同走进霍尔顿的世界吧!

    01.成功的家庭教育:孩子任何时候都能回家

    小说主人公的人物设定很成功。霍尔顿出身中产,父亲是律师,富有教养的母亲,可爱聪明的妹妹。庭家境殷实,物质丰富,16岁就读名校。这样的人设放就是放在今天也是大多数的人难以企及的。在普通人眼中衣食无忧的他应该过着无忧无虑的生活,

    但是痛苦从来不分阶层,不会被金钱和物质左右。

    富家子弟霍尔顿从一开始就被失败和痛苦包裹着:连续4次被不同的学校开除,第四次被潘西开除是因为5门学科有四门不及格;作为击剑队的领队把比赛用的所有装备一股脑儿地落在了地铁上了。

    在离开学校之前,霍尔顿去见了历史老师,历史老师想给霍尔顿一些指引,无奈霍尔顿在药水味浓烈的房间里想着别的他认为更重要的事——冰湖上的野鸭。

    潘西校长已经写信给霍尔顿的父母告知有关霍尔顿被开除的事情,为了不和情绪激动的父母有正面的交锋,他决定在学校待几天,因为父母需要一点时间来接受自己被潘西开除的消息。

    时逢周末,很多同学都回家了,室友斯特拉德莱塔忙着约会,无意中得知斯特拉德莱塔约会的女孩子是琴.迦拉格,霍尔顿整个人都紧张起来了。因为琴.迦拉格对她来说,就是一个喜欢到不忍心和别人分享的珍宝,就像王尔德说的那样:

    要是我骨子里喜欢什么,我无论如何都不会把她们的名字告诉别人,那就像把她们的一部分交了出去。别人的不屑一置,又会增加被蔑视感,因为我们的喜爱就像我们的一部分。”

    霍尔顿不愿想象他们约会的情境,因为他知道斯特拉德莱塔是一个怎样的人:

    “他的外貌总是挺不错,可你拿起他的剃刀看看。那剃刀锈得像块烂铁,沾满肥皂沫,胡子之类的脏东西。他私底下原是个邋遢鬼。”

    “他感兴趣的只是那些非常色情的东西。”

    虽然这样,霍尔顿还是保持着清醒和理智,直到作文事件的发生。作文事件 就像一颗导火索,引爆了神经早已紧绷的霍尔顿。


    起因是斯特拉德莱塔请求霍尔顿在他约会的时候帮他写一篇有关房间的文章,霍尔顿最终写的是垒球手套,一个他一直放在行李箱里珍藏的手套,一只写满诗歌的手套,一只他弟弟艾理的手套。约会回来的斯特拉德莱塔发现作文写的不是房间,大为光火:

    “天哪,霍尔顿。这写的是一只混账的垒球手套呢。”

    “真他妈的”他气的要命。他这次是真生气了。“你干的事情没一样对头,怪不得要把你他妈的开除出去。”

    霍尔顿保持沉默。

    但是霍尔顿按耐不住地打听有关斯特拉德莱塔和琴约会的情形,话题不断变得敏感,最终按捺不住内心的嫉妒, 霍尔顿突然打了斯特拉德莱塔一拳,战争爆发了,但只是一场实力悬殊的战争:

    “嗯,我记得的下一件事,就是我已经躺在混账地板上了,他满脸通红地坐在我胸脯上。那就是说他用他妈的两个膝盖压着我的胸脯,而他差不多有一吨重。他两手握住我的手腕,所以我不能再挥拳打他。”

    带着战败的羞愧霍尔顿走到隔壁室友阿克莱房间,想在那里住一晚,结果阿克莱的冷漠让他找不到可以留下来的理由。


    这些挫败感让他不能继续留在潘西,哪怕是多一秒也不能忍受。于是在圣诞节前雪花纷飞,寒冷的冬夜,霍尔顿坐上了回纽约的火车。

    愤然离校,有家难回的霍尔顿能去哪儿?

    大家是否有过这样的体会,在最无助和痛苦的时候,想找人倾诉,却不知道电话要打给谁?想离开令人窒息的地方,却不知道能去哪儿?内心的压抑似乎让呼吸都变得困难。我想霍尔顿当时的心境大致如此。如果我们的孩子也遇到这样的情况,他们能够毫无顾虑地,满怀信任的回到自己的家吗?相信家是可以疗伤的地方,爸爸妈妈是最温暖的存在,他们不是独自一人?

    孤独和挫败并没有因为霍尔顿的离开而留在了潘西,等待他的是更深的孤独,更大的伤害和失败。


    02冬季湖面结冰,鸭子会去哪里?

    在和历史老师见面的时候,霍尔顿一面听着老师的说教,一面想着中央公园里湖面上的鸭子:“我一边信口开河,一边却在想别的事。我一个劲儿琢磨,湖水冻严以后,那些野鸭到底上哪儿去了。我琢磨是不是会有人开了辆卡车来,捉住它们送到动物园里去。或者竟是它们自己飞走了?”

    故事发生在寒冷的冬季,被退学的霍尔顿的境遇也正如这寒冷的季节一样。被失败和孤独冰封的霍尔顿会怎样?就像那湖面上的鸭子,会去哪里?会被坏人捉去吗?不得不说作者在小说的开头做了一个巧妙的铺垫。

    到了纽约的霍尔顿两次问不同的出租车司机有关鸭子的问题,其实这正是他自己内心的疑问,他到底该怎么办?到底该去哪里?很可惜出租车司机没有给他他想要的答案。

    自始至终琴.迦拉格占据着他的内心:

    “一霎时,在我出去到休息室的半路上,我脑子里忽然又想起了老琴.迦拉格来。她进了我的脑子,却再也不肯出去。”


    孤独和思念成为挥之不去的折磨,只有更为狂躁和喧嚣的刺激才能暂时平息他的无助和痛苦,于是霍尔顿走进黑夜里的酒吧,从一家到另一家,不能有片刻的间断,因为思念的痛会随时从缝隙里钻出来,令人窒息。

    伤害尾随着黑夜悄然而至。霍尔顿从酒吧回来后在电梯里遇到了电梯管理员兼皮条客毛里斯。三言两语就敲定了细节。一个叫桑妮的女孩如约而至。结果两个人之间只是进行了一次有偿付费的对话。(作者在后面霍尔顿的和老路斯见面时明确的说明了原因)。但双方因为金额发生了争执。

    后来毛里斯再次和女孩一起出现在霍尔顿的房间,最终以霍尔顿重重的挨了一拳并被抢走一张5元的钞票而结束。但对霍尔顿来说不仅仅如此:

    “我还在哭,我是他妈的那么生气,那么紧张。”

    “我的话还没说完,他就揍了我一拳”

    “去浴室的半路上,我开始幻想自己心窝里中了一颗子弹”

    “我当时倒是真想自杀。我很想从窗口跳出去。”

    霍尔顿离开了这个让他伤心和害怕到想去死的旅馆,因为他不想再见到毛里斯了。当时的时间是周日。要熬到周三才能回家的霍尔顿此时不知道该去哪儿,该干什么。

    约朋友见面成为唯一可以消磨时光的事情,在不知道该怎么办的时候,人往往寄希望于他人。


    03哪一条才是现实的救赎之路

    从旅馆出来以后,霍尔顿先后见了三位朋友。第一位是萨丽。霍尔顿打电话约了萨丽下午两点见面。在等待的时间里他去了公园和博物馆,希望见到她的妹妹菲苾,因为菲苾每个星期天都会到公园去。

    到了下午,霍尔顿和萨丽一起看戏, 溜冰,但最后两人却不欢而散。原因是霍尔顿提出的任何问题萨丽都无法苟同,直到最后霍尔顿想过一种与世无争的生活:

    “等到钱用完了,我可以在哪儿找个工作做,咱们可以在溪边什么地方住着。过些日子咱们还可以结婚。到冬天我可以亲自出去打柴。老天爷,我们能过多么美好的生活!你看呢?说吧!你愿不愿意跟我一块去?”



    老萨丽的回答让霍尔顿务无比失望:“你怎么可以干这样的事呢?第一,咱们两个简直还都是孩子。再说,你可曾想过,万一你把钱花光了,可又找不到工作,那时你怎么办?咱们都会活活饿死。这简直是异想天开,连一点儿----”

    霍尔顿最后的话把萨丽气走了:“喂,咱们走吧!你真是讨人厌极了,我老实告诉你说。”

    为什么霍尔顿会说出如此没有理智的话?那是因为他期望中的摆脱现实困境的救赎之路,被萨丽用现实的理由牢牢地堵上了。而且霍尔顿心里明白,萨丽说的没错。

    之后霍尔顿约见了老卡尔.路斯。路斯是霍尔顿在胡敦中学念书时的辅导员。就如霍尔顿所说,老路斯只做一件事,就是在夜深人静的时候在他的房间里纠集一帮人大谈性问题。

    老路斯的女朋友是中国人,因此他们讨论到东西方的有关哲学和性的问题,正如老路斯所说,东方不同于西方的观念之一就是东方把性看成是肉体和精神的双重关系。因此在西方思想下霍尔顿是不能理解自己的纯真无邪的行为的,反而怀疑自己:

    “你知道我的毛病在哪儿?跟一个我并不太喜欢的姑娘在一起,我始终没有真正的性欲——我是说真正的性欲。我是说我得先喜欢她。要是不喜欢,我简直对她连一点点混账的欲望都没有。嘿,我的性生活真是一塌糊涂。”

    最后老路斯建议霍尔顿和精神分析家聊聊,比如他的爸爸。

    最后霍尔顿去见了安东里尼老师。安东里尼老

    师是霍尔顿默认的精神导师, 因为在他的通讯录中,只有三个人的地址,琴.迦拉格,父亲办公室,还有一个就是安东里尼老师。

    安东里尼老师认为确定人生目标+学习是最好的走出现实困境方式。


    “你得找出你想要去的地方。你决不能在浪费一分钟时间了。尤其是你。”“一旦你弄清了要去哪儿,第一件事情就应该是在学校里学习。当你学习以后就会知道,对人类的行为感到迷茫,恐惧,甚至恶心的,你并不是第一个。历史上有许许多多的人都像你现在这样,在道德和精神上都有过彷徨的时期。”


    而且用一句话教育霍尔顿如何处理现实和梦想之间的关系。

    “一个不成熟男人的标志是他愿意为某种事业英勇地死去;一个成熟男人的标志是他愿意为了某种事业而卑贱地活着。”

    现实生活中有很多逃离现实而过着行尸走肉的人,但更多的人为着自己的梦想,家庭,孩子和爱人忍受着生活的压力和社会的堡垒默默的坚持着,他们也许是一个个风雨无阻的外卖骑手,一个个黑着眼圈的程序员......

    04.霍尔顿心里大喊:“我真他妈快乐”


    很多人都说《麦田里的守望者》只写了霍尔顿的困惑,却没有给出解决的办法。我却不敢苟同。因为霍尔顿在小说的结尾得到发自内心的快乐:

    突然间我变得他妈的那么快乐,眼看着老菲苾那么一圈圈转个不停。我险些儿他妈的大叫大嚷起来,我心里实在快乐极了。”

    最终当他看到妹妹菲苾开心地骑着旋转木马的时候,他变得快乐了,因为他找到了自己想做的事,想去的地方。他不再是那个没有方向,没地方可去的莽撞少年了。

    不管怎样,我老是在想象,有那么一群小孩在一大块麦田里做游戏。几千几万个小孩,附近没有一个人---一个大人,我是说---除了我。我呢,就站在那个混账的悬崖边。我的职务是在那儿守望,要是有哪个孩子往悬崖边奔来,我就把他捉住。我是说孩子们都在狂奔,也不知道自己是往那儿跑。我只想当个麦田里的守望者。”

    这时的霍尔顿找到了自己心中久久渴望的事情,做一个守望者,保护着弱小无邪的孩子。看着在旋转木马上菲苾的霍尔顿这时有如那麦田中守望着孩子们的守望者,无疑他是快乐的,幸福的,因为他找到了自我价值的所在。


    只有变成更好的人,才能做一个好的守望者。

    通过爱他人,救赎他人而实现自己的救赎。霍尔顿的救赎在妹妹身上找到了。

    小说隐喻式的结尾给全书做了一个完美的闭环。

    在所有人都跑去避雨的时候,霍尔顿却独自在雨中接受洗礼:“嘿,雨开始下大了。是倾盆大雨,我可以对天发誓。我身上都湿透了。不过我并不在乎。”

    大雨就是圣洁的象征,它能冲刷一切黑暗和阴霾,就像《狮子王》中的结尾,当辛巴战胜刀疤站在荣耀石上发出吼叫的时候,一场大雨如期而至,冲刷了刀疤统治时的黑暗,战斗留下的鲜血。电影《肖申克的救赎》中,当安迪从下水道爬出监狱以后,等待他的也是一场暴风雨。在大雨中,安迪仰头,双眼紧闭,最大程度的伸开双臂去接受暴雨的洗礼。大雨过后,是新的开始和时代,霍尔顿也将开始新的生活。

    罗曼罗兰曾说:“真正的英雄主义就是在看清现实以后依然热爱它!”

    《麦田里的守望者》自出版以来就受到不同国家,不同时代,不同身份和背景的众多读者的喜爱和追捧,其秘密就是书中设置的有关人性永恒的主题:平等与博爱,精神和肉体,梦想与现实。这些主题超越国界,超越肤色,超越文化,引领着读者探寻着人性之中至纯的真善美,去感受那深藏在戴着鸭舌帽,说着脏话的16岁少年内心的爱和纯真。

    如果你因为读了《麦田里的守望者》而给你的孩子一个大拥抱的话,我想霍尔顿.考尔菲德一定会把他那一直反着戴的红色鸭舌帽转过来,然后给你一个绅士般的行礼。

    作为最广泛支持的操作系统,HarmonyOS目前用在哪些产品了?

    $
    0
    0

    6月2日晚,华为在线上举办 HarmonyOS 2 及华为全场景新品发布会,从发布会我们获知,原本只用于智慧屏、可穿戴设备等产品的HarmonyOS系统已经可以在更多产品品类上使用。

    华为消费者业务CEO余承东表示,过去10年是转型的10年,华为引领了手机的多项创新,比如摄像、通信、解决安卓卡顿,续航、快充、新材料新工艺、折叠屏等;未来十年华为消费者业务的战略是 全场景1+8+N智慧生活解决方案,重点围绕智能家居、智慧办公、智慧出行、运动健康、影音娱乐5大生活场景来构筑体验。而 HarmonyOS 2的推出,支持多设备多硬件,解决PC、手机、平板、手表等系统之间的体验割裂问题,同时还将支持大量的 IoT设备。

    华为消费者业务软件部总裁王成录表示, HarmonyOS 2推出后,无论设备大小,只需一个系统。除了HarmonyOS 2,没有其它系统能够做到这点。据介绍,鸿蒙操作系统是一个全栈解耦的架构,一套代码可以在手机上使用,也可以在手表上使用,以及很多小设备上使用。依靠其统一控制中心,多设备之间可以组成超级终端,从而选择最适合的设备。

    HarmonyOS 2采用了分布式技术,通过软总线将独立设备连接起来。用户可以根据需要自由组合硬件,比如手机连接无人机摄像头和运动相机,进行多机位拍摄;PC、平板和手机连接一起,协同办公;手机与冰箱的连接,智能推荐不同食材的温度。值得一提的是,对开发者而言,HarmonyOS可以实现一次开发、多端部署,让多设备应用开发更加简单。

    HarmonyOS的真实实力如何,我们只能通过在众多搭载HarmonyOS的硬件中去体验才能得知。本次发布会上,华为消费者业务COO何刚介绍了多款搭载HarmonyOS 2的新产品,包括HUAWEI Mate 40系列新版本、Mate X2新版本、HUAWEI WATCH 3系列、HUAWEI MatePad Pro等手机、智能手表、平板产品。同时带来了新一代半开放主动降噪无线蓝牙耳机HUAWEI FreeBuds 4和两款高端显示器HUAWEI MateView和MateView GT。

    Linux - 系统指标 CPU load - 简书

    $
    0
    0

    cpu load

    cpu load通常做为一个机器负载的衡量指标

    cpu load是对使用或者等待cpu进程的统计(数量的累加)。每一个使用(using)或者等待(waiting)CPU的进程(process),都会使load值+1。
    每一个结束的(teminates)进程,都会使load值-1。

    所谓使用CPU的进程,是指状态为 running的进程,或者说是在cpu run queue里的进程。
    所谓等待CPU的进程,是指状态为 runnable的进程,或者说是在cpu ready queue里的进程。

    大部分Unix操作系统只计算 runningrunnable的进程。但是Linux系统除了上述两种状态进行,还计算 uninterruptible sleep状态的进程(通常是在等待磁盘IO)。因此,如果有很多进程被block在IO处,Linux系统显示的load会被Unix系统高一些。举个例子,如果有进程由于NFS服务挂掉或者USB设备太慢而block住的话,会显示一个奇怪的现象: cpu使用率不高,但是cpu load很高

    cpu load average

    通常,我们关注的不是cpu load,而是cpu load average这个指标。

    所有的Unix和类Unix操作系统,都有3 个"load average"的统计指标。分别表示 1分钟、5分钟和15分钟内的cpu负载均值。

    使用 uptime命令是查看cpu负载最简单的命令:

    $ uptime
    14:34:03 up 10:43, 4 users, load average: 0.06, 0.11, 0.09

    当然, wtop命令也包含cpu load信息。

    top命令:


    image.png

    w命令:


    image.png

    load average的意义

    load average是个无量纲的,大致可认为是cpu进程队列中进程的数量和cpu可处理能力的比值。比如cpu最多同时能处理10个进程,

    • 如果队列中进程数量为5,load average为50%,说明cpu还没达到负载上限
    • 如果队列中进程数量为10,load average为100%,说明刚好达到负载上限
    • 如果队列中进程数量为15,load average为150%,说明超出了cpu的处理能力

    单个cpu的load average为0.7以上时,需要注意是否快达到了服务能力的瓶颈。

    cpu load和cpu utilization

    cpu load(cpu负载)相较于cpu utilization(cpu使用率)更能反映机器的负载情况。

    因为,假如有两个配置相同的机器,一个机器cpu utilization是50%,另一个是70%,我们可以认为70%的机器负载比50%的高。但是如果两个机器的cpu use都是100%的话,谁的负载更高,就不得而知了。而cpu load是可以反映出来的。

    比如两个机器的per cpu load分别是1.5和1.8,但是他们的cpu use都是100%。但是我们依旧可以知道哪台机器负载更高。

    假如在一个单核机器上,load average是"1.73 0.60 7.98",那么可以大致这么解释:

    • 在过去1分钟中,系统负载超额73%。(1.73个runnable进程,但是有0.73个进程需要等待)
    • 在过去5分钟中,系统负载有40%空闲
    • 在过去15分钟中,系统负载超额698%。(7.98个runnable进程,但是有6.98个需要等待)

    参考

    TCP的高性能机制

    $
    0
    0

    之前介绍了 TCP 的报文格式(《TCP 协议基本特性》),TCP 的连接管理,学习了 TCP 如何建立连接,释放连接以及一些网络安装方面的问题,现在还剩下 TCP 的几个关键机制,主要是 TPC 的延迟应答和捎带应答、超......

    之前介绍了 TCP 的报文格式( 《TCP 协议基本特性》),TCP 的连接管理,学习了 TCP 如何建立连接,释放连接以及一些网络安装方面的问题,现在还剩下 TCP 的几个关键机制,主要是 TPC 的延迟应答和捎带应答、超时重传、快重传和快恢复、滑动窗口机制、拥塞避免算法;然后最后还记录了 TCP 的粘包问题和解决方案!

    TCP 实现的可靠传输其实依赖于确认应答机制,也就是接收方每次接收完数据之后会回发一个 ack 确认号,代表自己下次期望收到的序号,意思是告诉发送者, 我已经收到了哪些数据; 下一次你从哪里开始发。 TCP 数据包中的序列号(Sequence Number)不是以报文段来进行编号的,而是将连接生存周期内传输的所有数据当作一个字节流,序列号就是整个字节 流中每个字节的编号。一个 TCP 数据包中包含多个字节流的数据(即数据段),而且每个 TCP 数据包中的数据大小不一定相同。在建立 TCP 连接的三次握手 过程中,通信双方各自已确定了初始的序号 x 和 y,TCP 每次传送的报文段中的序号字段值表示所要传送本报文中的第一个字节的序号。

    TCP 的确认应答

    TCP 提供的确认机制,可以在通信过程中可以不对每一个 TCP 数据包发出单独的确认包(Delayed ACK 机制),而是在传送数据时,顺便把确认信息传出, 这样可以大大提高网络的利用率和传输效率。同时,TCP 的确认机制,也可以一次确认多个数据报,例如,接收方收到了 201,301,401 的数据报,则只 需要对 401 的数据包进行确认即可,对 401 的数据包的确认也意味着 401 之前的所有数据包都已经确认,这就是延迟应答,这样也可以提高系统的效率。

    再说说捎带应答,TCP 的确认应答和回执数据可以通过一个包发送。

    TCP 的超时重传

    若发送方在规定时间内没有收到接收方的确认信息,就要将未被确认的数据包重新发送。接收方如果收到一个有差错的报文,则丢弃此报文,并不向发送方 发送确认信息。因此,TCP 报文的重传机制是由设置的超时定时器来决定的,在定时的时间内没有收到确认信息,则进行重传。这个定时的时间值的设定非常重要,太大会使包重传的延时比较大,太小则可能没有来得及收到对方的确认包发送方就再次重传,会使网络陷入无休止的重传过程中。接收方如果收到 了重复的报文,将会丢弃重复的报文,但是必须发回确认信息,否则对方会再次发送。

    但是主机 A 未收到 B 发来的确认应答, 也可能是因为 ACK 丢失了;因此主机 B 会收到很多重复数据。那么 TCP 协议需要能够识别出那些包是重复的包,,并且把重复的丢弃掉。这时候我们可以利用前面提到的序列号, 就可以很容易做到去重的效果。

    超时时间如何确定?

    TCP 为了保证无论在任何环境下都能比较高性能的通信,因此会动态计算这个最大超时时间:Linux 中 (BSD Unix 和 Windows 也是如此),超时以 500ms 为一个单位进行控制,每次判定超时重发的超时时间都是 500ms 的整数倍,如果重发一次之后,仍然得不到应答,等待 2*500ms后再进行重传如果仍然得不到应答,等待 4*500ms进行重传。依次类推以指数形式递增,累计到一定的重传次数,TCP 认为网络或者对端主机出现异常,强制关闭连接

    TCP 快速重传(冗余 ACK)

    有了超时重传机制为什么还出现了快速重传呢?其实这也是 TCP 为了效率的一种保障,每当比期望序号大的失序报文段到达时,发送一个冗余 ACK,指明下一个期待字节的序号。

    超时重传是底线,是功能性的,但是快重传是建立在超时重传上的,为了效率的提高

    TCP 流量控制

    接收端处理数据的速度是有限的。如果发送端发的太快,导致接收端的缓冲区被打满,这个时候如果发送端继续发送,就会造成丢包,继而引起丢包重传等等一系列连锁反应。因此 TCP 支持根据接收端的处理能力,来决定发送端的发送速度。这个机制就叫做流量控制 (Flow Control);

    在通信过程中,接收方根据自己接收缓存的大小,动态地调整发送方的发送窗口大小,即接收窗口 rwnd (接收方设置确认报文段的窗口字段来将 rwnd 通知给发送方), 发送方的发送窗口取接收窗口 rwnd 和拥塞窗口 cwnd 的最小值。接收窗口也就是接收方的接收缓冲区,拥塞窗口简单来说就是网络堵塞了,那就是说明在这个网络中使用网络带宽的主机很多,或者占用了很多资源,导致发送缓慢,那么这就是一个网络拥塞的情况

    TCP 首部中,专门有一个窗口大小的字段用来通知窗口大小。接收主机将自己可以接收的缓冲区大小放人这个字段中通知给发送端。这个字段的值越大,说明网络的吞吐量越高。不过,接收端的这个缓冲区一旦面临数据溢出时,窗口大小的值也会随之被设置为一个更小的值通知给发送端,从而控制数据发送量。也就是说,发送端主机会根据接收端主机的指示,对发送数据的量进行控制。这也就形成了一个完整的 TCP 流控制 (流量控制)。

    从图中可以看到,如果过了重发时间还没有收到窗口的更新通知,会发送一个探测报文去探测接收方的窗口。

    滑动窗口虽然只能往右滑,但是可能变大,也可能变小,也可能是 0;收到第一个 ACK 后,滑动窗口向后移动,继续发送第五个段的数据,依次类推,操作系统内核为了维护这个滑动窗口,需要开辟发送缓冲区来记录当前还有哪些数据没有应答;只有确认应答过的数据,才能从缓冲区删掉;窗口越大,则网络的吞吐率就越高;

    TCP 拥塞控制

    因为网络上有很多的计算机,可能当前的网络状态就已经比较拥堵。在不清楚当前网络状态下,贸然发送大量的数据,是很有可能引起雪上加霜的。一般来说,计算机网络都处在一个共享的环境。因此也有可能会因为其他主机之间的通信使得网络拥堵。在网络出现拥堵时,如果突然发送一个较大量的数据,极有可能会导致整个网络的瘫痪看看 TCP 是如何解决这个问题的呢?

    TCP 引入了慢启动机制:先发少量的数据,探探路,摸清当前的网络拥堵状态,再决定按照多大的速度传输数据;

    那么慢启动和拥塞避免的算法如下图所示:

    cwnd 指的是一个报文段(最大报文段长度 MSS),最开始发一个报文段然后呈指数式增长到 ssthresh 初始值,这就是慢启动,然后执行加法增大,也就是拥塞避免阶段,达到阈值的时候变会触发快重传,此时降到新的 ssthresh 值,即为拥塞时的一半,这就是快恢复策略!当 TCP 通信开始以后,网络吞吐量会逐渐上升,但是随着网络拥堵的发生吞吐量也会急速下降。于是会再次进人吞吐量慢慢上升的过程。因此所谓 TCP 的吞吐量的特点就好像是在逐步占领网络带宽的感觉。

    TCP 粘包问题

    首先要明确,粘包问题中的” 包” 是指的应用层的数据包。

    在 TCP 的协议头中,没有如同 UDP 一样的” 报文长度” 这样的字段,但是有一个序号这样的字段。站在传输层的角度,TCP 是一个一个报文过来的,按照序号排好序放在缓冲区中。站在应用层的角度,看到的只是一串连续的字节数据,那么应用程序看到了这么一连串的字节数据,就不知道从哪个部分开始到哪个部分是一个完整的应用层数据包

    那么如何避免粘包问题呢? 归根结底就是一句话,明确两个包之间的边界:

    • 对于定长的包,保证每次都按固定大小读取即可;例如对于一个结构体 struct,是固定大小的,那么就从缓冲区从头开始按 sizeof(struct) 依次读取即可;
    • 对于变长的包,可以在包头的位置,约定一个包总长度的字段,从而就知道了包的结束位置;
    • 对于变长的包,还可以在包和包之间使用明确的分隔符 (应用层协议,是程序猿自己来定的,只要保证分隔符不和正文冲突即可);

    对于 UDP 是否也存在” 粘包问题” 呢?对于 UDP,如果还没有上层交付数据,UDP 的报文长度仍然在。同时,UDP 是一个一个把数据交付给应用层,就有很明确的数据边界。站在应用层的角度,使用 UDP 的时候,要么收到完整的 UDP 报文,要么不收,不会出现” 半个” 的情况。


    译|High-Performance Server Architecture

    $
    0
    0

    介绍

    本文的目的是分享我多年来关于如何开发某种应用程序的一些想法,对于这种应用程序,术语“服务”只是一个无力的近似称呼。更准确地说,将写的与一大类程序有关,这些程序旨每秒处理大量离散的消息或请求。网络服务通常最适合此定义,但从某种意义上讲,实际上并非所有的程序都是服务。但是,由于“高性能请求处理程序”是很糟糕的标题,为简单起见,倒不如叫“服务”万事大吉。

    尽管单个程序中多任务处理现在很普遍,但我不会写此类“轻度并行”应用程序。您用来阅读本文的浏览器可能会并行执行某些操作,但是如此低的并行度真的不会带来多少有趣的挑战。当请求处理的基础结构本身是整体性能的瓶颈时,就会出现有趣的挑战,因此改进基础结构实际上会提高性能。对于运行在具有千兆位内存的千兆赫处理器上的浏览器,通过 DSL 线路同时进行六路下载,基础结构成为瓶颈并不常见。此文关注的重点不是用吸管抿水的应用程序,而是从消防水管喝水的应用程序。在硬件功能的边缘,如何做才是真正重要的。

    有些人不可避免地会对我的一些意见和建议持怀疑态度,或者认为他们有更好的方法。挺好,我不是想成为上帝的代言人;我发现这些方法对我来说很有用,不仅是它们对性能的影响,而且它们对以后调试或扩展代码的难度也有影响。效果因人而异。如果还有其他方法对您更好,那太好了,但是请注意,我在这里建议的几乎所有方法都曾作为其他方法的替代品而存在,而我曾经尝试过,只是其结果让人厌恶或恐惧。你最喜欢的想法可能会在其中某个故事中占据显著位置,如果让我现在就讲述出来,无辜的读者可能会无聊至死。您不想伤害他们,对吗?

    本文的其余部分将围绕我称之为“性能糟糕的四骑士”展开:

    译者注:天启四骑士,战争、瘟疫、饥荒和死亡。

    1. 数据拷贝
    2. 上下文切换
    3. 内存分配
    4. 锁竞争

    最后还将有一个总括的章节,但这些是最大的性能杀手。如果您能够处理大多数请求而无需数据拷贝,无需上下文切换,无需经过内存分配器并且无需竞争锁,那么即使有一些次要问题,您也会拥有一个性能良好的服务。

    数据拷贝

    这可能是一个很短的章节,原因很简单:大多数人已经吸取了这个教训。人人都知道数据拷贝不好。很明显,对吧?实际上,显而易见可能是您在计算机职业生涯的很早就知道,仅仅是因为有人在几十年前就开始使用这个词了。我知道我的情况就是如此,但我离题了。如今,每门学校课程和每个非正式的指南都涵盖了它。甚至营销人员也发现“零拷贝”是一个很好的热门词汇。

    尽管事后看来副本很糟糕,但似乎仍然有些让人错过的细微差别。其中最重要的是,数据拷贝经常是隐藏和伪装起来的。您真的知道您调用的驱动程序或库中的代码是否会进行数据拷贝吗?可能超出您的想象。猜猜PC上的“编程 I/O”是指什么。哈希函数是伪装、非隐藏副本的一个示例,该函数具有副本的所有内存访问开销,并且还涉及更多的计算。一旦指出散列实际上是“拷贝升级版”,显然应该避免使用散列,但我知道至少有一群才华横溢的人,他们必须用艰难的方式来解决这个问题。如果您真的想摆脱数据拷贝,不管是因为它真的会影响性能,还是因为你想把“零拷贝操作”写入黑客会议幻灯片里,您将需要跟踪许多真正属于数据拷贝但并未广而告之的内容。

    避免数据拷贝行之有效的方法是使用间接寻址,并传递缓冲区描述符(或缓冲区描述符链),而不是仅仅使用缓冲区指针。每个描述符通常由以下内容组成:

    • 整个缓冲区的指针和长度。
    • 缓冲区的实际填充部分的指针和长度,或偏移量和长度。
    • 指向列表中其他缓冲区描述符的前后指针。
    • 引用计数。

    现在,代码只需将适当的缓冲区描述符的引用计数加一,而不用拷贝一段数据以确保它留在内存中。在某些情况下,这种做法可以非常好地工作,包括典型的网络协议栈的运行方式,但也可能成为一个真正令人头痛的问题。一般来说,很容易在链的开始或结尾添加缓冲区,添加对整个缓冲区的引用以及释放整个链。在中间添加,逐块释放或引用部分缓冲区愈加困难。尝试拆分或合并缓冲区简直让人发疯。

    不过,我实际上并不建议所有情况都使用这种方法。为什么不?因为每次要查看头字段时都必须遍历描述符链,这将成为极大的痛苦。确实有比数据拷贝更糟糕的事情。我发现最好的方法是识别程序中的大对象,例如数据块,确保这些大对象按上述方法单独进行分配,这样就不必拷贝它们,也不必过多地操心其他事情。

    这就引出了我关于数据拷贝的最后一点:不要过分规避。我已经看到太多的代码通过做更糟糕的事情来避免数据拷贝,例如强制执行上下文切换或中断大型 I/O 请求。数据拷贝代价很高,当您寻找避免冗余操作的地方时,它是您应首先考虑的问题之一,但是收益递减。对代码进行梳理,然后将其复杂度提高一倍,仅仅是为了去掉最后几份数据副本,通常是在浪费本可以更好利用在其他地方的时间。

    上下文切换

    尽管每个人都认为数据拷贝很糟糕,但我却常常为这么多人完全忽略上下文切换对性能的影响而感到惊讶。根据我的经验,在高负载下,上下文切换实际上比数据副本要落后更多的“崩溃”。系统从一个线程到另一个线程所花费的时间,开始多于它在线程内实际执行有用工作所花费的时间。令人惊奇的是,在某种程度上,导致过度上下文切换的原因是显而易见的。上下文切换的第一大原因是活跃线程数多于处理器数。随着活跃线程与处理器的比率增加,上下文切换的数量也会增加——运气好的话会呈线性关系,但通常呈指数关系。这个非常简单的事实解释了为什么每个连接一个线程的多线程设计的伸缩性非常差。对于可伸缩系统来说,唯一可行的选择是限制活动线程的数量,使其(通常)小于或等于处理器的数量。这种方法的一种流行变体是永远只使用一个线程。尽管这种方法确实避免了上下文抖动,也避免了加锁,它也无法实现超过一个处理器的总吞吐量。因此,除非该程序无论如何都是非 CPU 密集型的(通常是网络I/O密集型的),否则它仍然不受重视。

    “线程有度”的程序要做的第一件事就是弄清楚如何让一个线程同时处理多个连接。这通常意味着前端使用 select/poll、asynchronous I/O、信号或完成端口,后端是事件驱动的结构。哪种前端 API 最好,许多“宗教战争”已经打过,而且还在继续。Dan Kegel 的 C10K论文
    是该领域很好的资料。就个人而言,我认为所有 select/poll 和 signal 形式都是丑陋的,因此偏爱 AIO 或完成端口,但实际上并不重要。除了 select(),其他都可以很好地工作,处理程序前端最外层不需要做太多的工作。

    多线程事件驱动服务最简单概念模型是在其中心处有一个队列。一个或多个 “listener” 线程读取请求并将其放入队列,一个或多个 “worker” 线程将其从中移除并处理。从概念上讲,这是一个很好的模型,但是人们通常经常以这种方式实现他们的代码。为什么这样做是错的呢?因为上下文切换的第二大原因是将工作从一个线程转移到另一个线程。有些人甚至要求由原始(译者注:listener)线程发送请求的响应,使错误更严重 —— 导致每个请求需要两次上下文切换而非一次。使用“对称的”方法非常重要,在这种方法中,给定线程可以在不更改上下文的情况下,从 “listener” 成为 “worker”,再成为 “listener”。

    通常,即使将来的一瞬间,也不可能知道有多少个线程处于活跃状态。毕竟,请求可能随时出现在任何连接上,也可能专用于各种维护任务的“后台”线程在那一刻唤醒。如果您不知道有多少线程处于活跃状态,该如何限制有多少活跃线程?以我的经验,最有效也是最简单的方法之一:使用老式的计数信号量,每个线程在执行“实际工作”时都必须持有该信号量。如果已经达到线程限制,则每个 listen 模式线程可能会在唤醒时可能会产生一个额外的上下文切换,然后阻塞在信号量上,但是一旦所有 listen 模式线程都以这种方式阻塞,它们就不会继续争用资源,直到一个现有线程“退出”,因此系统影响可以忽略不计。更重要的是,这种方法处理维护线程比大多数替代方法更优雅(大部分时间处于睡眠状态,因此不计入活跃线程数)。

    一旦将请求处理分为两个阶段(listener 和 worker),并使用多个线程为这些阶段提供服务,就很自然地将处理进一步分为两个以上的阶段。在最简单的形式下,处理一个请求就变成了在一个方向上依次调用各个阶段,然后又在另一个方向上进行调用(对于应答)的问题。但是,事情会变得更加复杂。一个阶段可能代表 “fork”出来两条处理路径的两个互不相同的阶段,或者本身可能会在不调用其他阶段的情况下生成应答(例如,缓存的值)。因此,每个阶段都必须能够指定请求“下一步应该做什么”。由阶段的派发函数的返回值表示,有三种可能:

    • 该请求需要传递到另一个阶段(返回值中报站指示阶段的ID或指针)。
    • 请求已完成(特殊的“请求处理完毕”返回值)
    • 请求被阻塞(特殊的“请求阻塞”返回值)。与前面的情况相同,只是请求没有被释放,稍后将由另一个线程继续执行。

    请注意,在本模型中,请求的排队是在阶段内,而非阶段之间。避免了将请求不断放在后继阶段的队列中,然后立即调用该后继阶段,再次使请求出队的常见愚蠢做法。我称之为没事找事的队列、锁定活动。

    将一个复杂的任务分成多个较小的通信部分的想法似乎很熟悉,那是因为它实际上已经很久远了。我的方法源于 1978 年 C.A.R. Hoare 提出的“ Communicating Sequential Processes”概念,该概念又基于 Per Brinch Hansen 和 Matthew Conway 的思想,这些思想可以追溯到 1963 年 —— 我出生之前!但是,当 Hoare 创造术语 CSP 时,他的意思是抽象数学意义上的“进程”,并且 CSP 进程不必与同名的操作系统实体相关。在我看来,通过单 OS 线程内部类线程的协程以实现 CSP 的常见方法给用户带来了并发的所有麻烦,却又没有任何可伸缩性。

    同一时期,Matt Welsh 的 SEDA是一个朝着更明智的方向发展的阶段执行理念的例子。实际上,SEDA 是“正确完成服务架构”的一个很好的例子,它的一些特定的特征值得评论(尤其是那些与我上面概述的特征不同的地方)。

    1. SEDA 的“批处理”倾向于强调一次处理多个请求,而我的方法倾向于强调一次处理单个请求的多个阶段。
    2. 在我看来,SEDA 的一个显著缺陷是,它为每个阶段分配了一个单独的线程池,只在后台重新分配各个阶段的线程以响应负载。因此,上面提到的引起上下文切换的“1”和“2”原因仍然存在。
    3. 在学术研究项目的背景下,用 Java 实现 SEDA 可能说得通。但是,在现实世界中,这种选择可谓不恰当的。

    内存分配

    分配和释放内存是许多应用程序中最常见的操作之一。因此,人们已经开发出许多巧妙的技巧来使通用存储器分配器更有效。然而,再聪明也弥补不了这样一个事实:在许多情况下,这种分配器的通用性不可避免地使它们的效率远远低于其他分配器。因此,关于如何完全避免使用系统内存分配器,我有三点建议。

    建议一:简单的预分配。我们都知道,静态分配器如果导致程序功能受限,是非常不好的,但是还有许多其他形式的预分配可能会非常有益。通常,原因归结为这样一个事实:即使在此过程中“浪费”了一些内存,通过系统内存分配器的一次访问也要好于几次。因此,如果可以断言同时使用不超过N项,则在程序启动时进行预分配可能是一个有效的选择。即使不是这种情况,也可以在一开始就预先分配请求处理程序可能需要的所有内容,而不是根据需要分配每个内容。除了通过系统分配器在一次行程中连续分配多项的可能性之外,也通常大大简化了错误恢复代码。如果内存非常紧张,那么预分配可能不是一种选择,但在除最极端的情况外的所有情况下,结果通常都是净收益。

    建议二:对经常分配和释放的对象使用 lookaside 列表。基本思想是将最近释放的对象放到列表中,而不是真正释放,希望如果很快再次使用,则只需将其从列表中移除,而不用从系统内存中分配。另一个好处是, lookaside 列表的存取转换的实现通常可以跳过复杂的对象初始化/终结。

    通常不希望 lookaside 列表无限制地增长,即使程序处于空闲状态也从不释放任何内容。因此,通常有必要执行某种定期的 “sweeper” 任务以释放不活跃的对象,但是如果清理程序引入了不适当的加锁复杂性或竞争,则也不可取。因此,一个好的折衷方案是,lookaside 列表实际上由单独加锁的 “old” 列表和 “new” 列表组成的系统。优先从新列表开始分配,然后从旧列表开始分配,并且仅在万不得已的情况下才从系统中分配;对象总是被释放到新列表中。清理线程的操作如下:

    1. 锁定两个列表。
    2. 保存旧列表的表头。
    3. 通过表头赋值,将(以前)新列表变为旧列表。
    4. 解锁。
    5. 在空闲时将保存的旧列表中的所有对象都释放掉。

    此类系统中的对象只有在至少一个但不超过两个完整的清除程序间隔不需要时才真正释放。最重要的是,清除程序在执行大部分工作时没有持有任何与常规线程竞争的锁。理论上,相同的方法可以推广到两级以上,但我还没有发现如此做有用。

    使用 lookaside 列表的一个担心是列表指针可能会增加对象的大小。根据我的经验,使用 lookaside 列表的大多数对象都已经包含了列表指针,所以考虑此点没有实际意义。但是,即使指针只用于 lookaside 列表,但避免使用系统内存分配器(和对象初始化)方面所节省的开销,将远远弥补额外增加的内存。

    建议三:实际上与尚未讨论到的加锁有关,但我仍然要加进来。即使使用 lookaside 列表,锁竞争通常也是分配内存的最大成本。一种解决方案是维护多个私有的 lookaside 列表,这样就绝对不可能争用任何一个列表。例如,每个线程可以有一个单独的 lookaside 列表。出于高速缓存 cache-warmth 的考虑,每个处理器一个列表更好,但是仅在线程无法被抢占的情况下才有效。如有必要,私有 lookaside 列表甚至可以与共享列表相结合,以创建具有极低分配开销的系统。

    锁竞争

    众所周知,高效的加锁方案很难设计,因此我称之为 “Scylla” 和 “Charybdis”,取自《奥德赛》中的怪物。Scylla 是过于简单和/或粗粒度的锁,是可以或应该并行的串行化的活动,这些活动可以或应该并行进行,从而牺牲了性能和可伸缩性。Charybdis 是过于复杂或细粒度的锁,加锁的空间和加锁的操作时间会再次降低性能。靠近 Scylla 的陷阱是代表死锁和活锁的状态。靠近 Charybdis 的陷阱是代表竞态条件。两者之间,有一个狭窄的渠道代表既高效又正确的加锁……或者在哪?由于锁定往往与程序逻辑紧密相关,因此,如果不从根本上改变程序的工作方式,通常就不可能设计出良好的锁定方案。这就是为什么人们讨厌锁,并试图将不可伸缩的单线程实现合理化的原因。

    几乎每个加锁方案都是从“围绕所有事物的一个大锁”开始,并且茫然地希望性能不会太糟。当希望破灭时(几乎总是这样),大锁被分解成小锁,然后继续祈祷,然后重复整个过程,大概直到性能足够为止。但是,通常每次迭代都会增加 20-50% 的复杂性和锁开销,以减少 5-10% 的锁竞争。幸运的是,最终结果性能仍然会有些许提高,但实际下降的情况也并不少见。设计师只能挠头了,“我把锁力度做得更细,就像教科书上说的那样”,他想,“那为什么性能会变得更差呢?”

    我认为情况变得更糟,因为上述方法从根本上讲是错误的。把“解决方案空间”想象成一座山脉,高点代表好的解决方案,低点代表差的解决方案。问题是,“大锁”的起点几乎总是被各种山谷,马鞍山,小山峰、死胡同与高峰隔开。这是一个经典的爬山问题。想从一个起点爬到更高的山峰,只迈出一小步,从不走下坡路,几乎是行不通的。我们需要的是一种完全不同的接近顶峰的方式。

    您要做的第一件事是形成程序加锁的脑中地图。该地图有两个轴:

    • 纵轴表示代码。如果您使用的是非分支阶段的阶段体系结构,则可能已经有了一个显示划分的图表,就像每个人都在使用的 OSI 模型网络协议栈那样。
    • 横轴表示数据。在每个阶段中,应将每个请求分配给一个数据集,该数据集使用的资源应该独立于其他任何资源。

    现在有了一个网格,其中每个单元格表示特定处理阶段中的特定数据集。最重要的是以下规则:两个请求不应处于争用状态,除非它们位于相同的数据集和相同的处理阶段。如果你能做到这一点,你已经成功了一半。

    一旦定义了网格,就可以绘制程序的每种加锁类型,下一个目标是确保所得的点尽可能沿两个轴均匀分布。不幸的是,这部分是非常特定于应用的。你必须像钻石切割师一样思考,利用你对程序执行的知识来寻找阶段和数据集之间的自然“解理纹”。它们有时从一开始就很明显,有时很难找到,但回想起来似乎更明显。将代码分为多个阶段是一个复杂的程序设计问题,因此我能提供的内容不多,但以下是一些关于如何定义数据集的建议:

    • 如果有某种与请求相关联的块号或哈希或事务ID,那么最好将该值除以数据集的数量。
    • 有时,最好动态地将请求分配给数据集,根据哪个数据集拥有最多的可用资源,而不是请求的某些内在属性。把它想象成现代CPU中的多个整数单元;它们对离散请求流经系统略知一二。
    • 确保每个阶段的数据集分配不同通常是有用的,这样可以保证在一个阶段竞争的请求在另一阶段不会再次竞争。

    如果您已经将“加锁域”进行了垂直和水平划分,并确保加锁活动均匀地分布在生成的单元格中,则可以确定加锁状态良好。不过,还有一步。您还记得我几段内容之前嘲笑的“小步走”方法吗?它仍然有它的作用,因为现在你处于一个好的起点而不是一个糟糕的起点。用比喻的话来说,你可能已经爬上了这座山脉最高峰之一的斜坡,但你可能还没有到达山顶。现在是时候收集竞争的统计信息了,看看您需要做些什么来改进,以不同的方式拆分阶段和数据集,然后收集更多的统计信息,直到满意为止。如果你做了这些,你一定能从山顶看到美丽的景色。

    其他内容

    正如我所承诺的,我已经讨论了服务设计中四个最大的性能问题。不过,特定的服务仍然有其他重要的问题需要解决。主要是要了解平台/环境:

    • 存储子系统如何处理较大和较小的请求?顺序还是随机?read-ahead 和 write-behind 的能力如何?
    • 使用的网络协议的效率如何?是否可以设置参数或标志以获得更好的性能?是否有诸如TCP_CORK,MSG_PUSH 或 Nagle-toggling 技巧之类的工具可用于避免发送微小消息?
    • 系统是否支持分散/集中 I/O(例如readv / writev)?使用这些可以提高性能,也可以减轻使用缓冲链的痛苦。
    • 页大小是多少?缓存行大小是多少?在边界上内容对齐是否值得?相对于其他操作,系统调用或上下文切换的成本多高?
    • reader/writer 加锁原语是否处于饥饿?因何饥饿?事件有“惊群效应”的问题吗?睡眠/唤醒是否有一种恶劣的(但非常常见的)行为,即当 X 唤醒 Y 时,即使 X 还有事情要做,上下文也会立即切换到 Y?

    我相信我能想出更多这样的问题。相信你也可以。在任何特定情况下,针对任何一个问题做点什么都不值得,但通常至少值得考虑一下。如果您不知道答案 — 其中许多答案在系统文档中找不到 — 请找出答案。编写一个测试程序或微观基准,从经验上寻找答案;无论如何,编写这样的代码本身就是一种有用的技能。如果您要编写在多个平台上运行的代码,那么其中许多问题都与您应该将功能抽象到每个平台库中的点相关,这样您就可以在支持特定功能的平台上实现性能提升。

    “知道答案”理论也适用于你自己的代码。找出代码中重要的高级操作是什么,并在不同的条件下对它们进行计时。这与传统的概要性能剖析不太一样;这是衡量 设计元素,而不是实际的实现。低级优化通常是搞砸设计的人最后的选择。

    原文:High-Performance Server Architecture

    本文作者:cyningsun
    本文地址www.cyningsun.com/06-02-2021/…
    版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-ND 3.0 CN许可协议。转载请注明出处!

    使用 Istio 进行金丝雀部署

    $
    0
    0
    本篇博客最后更新时间 2018 年 5 月 16 号,采用了最新版本的流量管理模型。

    采用 Istio项目的一大好处就是为服务金丝雀方式部署提供了控制便利。金丝雀部署(或上线)背后的想法是通过让一小部分用户流量引入的新版本进行测试,如果一切顺利,则可以增加(可能逐渐增加)百分比,逐步替换旧版本。如在过程中出现任何问题,则可以中止并回滚到旧版本。最简单的方式,是随机选择百分比请求到金丝雀版本,但在更复杂的方案下,则可以基于请求的区域,用户或其他属性。

    基于领域的专业水平,您可能想知道为什么需要 Istio 来支持金丝雀部署,因为像 Kubernetes 这样的平台已经提供了进行 版本上线金丝雀部署的方法。问题解决了吗 ?不完全是。虽然以这种方式进行部署可以在简单的情况下工作,但功能非常有限,特别是在大规模自动缩放的云环境中大流量的情况下。

    Kubernetes 中的金丝雀部署

    假设我们有一个已部署的 helloworld服务 v1版本,我们想要测试(或简单上线)新版本 v2。使用 Kubernetes,您可以通过简单地更新服务的 Deployment中的镜像并自动进行部署来 上线新版本的 helloworld服务。如果我们特能够小心保证在启动并且在仅启动一个或两个 v2 副本 暂停上线时有足够的 v1副本运行,则能够保持金丝雀发布对系统的影响非常小。后续我们可以观察效果,或在必要时进行 回滚。最好,我们也能够对 Deployment 设置 HPA,在上线过程中减少或增加副本以处理流量负载时,也能够保持副本比例一致。

    尽管这种机制能够很好工作,但这种方式只适用于部署的经过适当测试的版本,也就是说,更多的是蓝/绿发布,又称红/黑发布,而不是 “蜻蜓点水“ 式的金丝雀部署。实际上,对于后者(例如,并没有完全准备好或者无意对外暴露的版本),Kubernetes 中的金丝雀部署将使用具有 公共 pod 标签的两个 Deployment 来完成。在这种情况下,我们不能再使用自动缩放器,因为是有由两个独立的自动缩放器来进行控制,不同负载情况下,副本比例(百分比)可能与所需的比例不同。

    无论我们使用一个或者两个部署,使用 Docker,Mesos/Marathon 或 Kubernetes 等容器编排平台进行的金丝雀发布管理都存在一个根本问题:使用实例扩容来管理流量;版本流量分发和副本部署在上述平台中并独立。所有 pod 副本,无论版本如何,在 kube-proxy循环池中都被一视同仁地对待,因此管理特定版本接收的流量的唯一方法是控制副本比例。以小百分比维持金丝雀流量需要许多副本(例如,1% 将需要至少 100 个副本)。即使我们可以忽略这个问题,部署方式功能仍然非常有限,因为它只支持简单(随机百分比)金丝雀部署。如果我们想根据某些特定规则将请求路由到金丝雀版本上,我们仍然需要另一种解决方案。

    使用 Istio

    使用 Istio,流量路由和副本部署是两个完全独立的功能。服务的 pod 数量可以根据流量负载灵活伸缩,与版本流量路由的控制完全正交。这在自动缩放的情况下能够更加简单地管理金丝雀版本。事实上,自动缩放管理器仍然独立运行,其在响应因流量路由导致的负载变化与其他原因导致负载变化的行为上没有区别。

    Istio 的 路由规则也带来了其他的便利;你可以轻松实现细粒度控制流量百分比(例如,路由 1% 的流量而不需要 100 个 pod),当然也可以使用其他规则来控制流量(例如,将特定用户的流量路由到金丝雀版本)。作为展示,让我们看一下采用这种方式部署 helloworld服务的简单便捷。

    首先我们定义 helloworld服务,和普通 Kubernetes服务一样,如下所示:

    apiVersion: v1
    kind: Service
    metadata:
    name: helloworld
    labels:
      app: helloworld
    spec:
      selector:
        app: helloworld
      ...

    然后我们添加 2 个 Deployment,分别为版本 v1v2,这两个版本都包含服务选择标签 app:helloworld

    kind: Deployment
    metadata:
      name: helloworld-v1
    spec:
      replicas: 1
      template:
        metadata:
          labels:
            app: helloworld
            version: v1
        spec:
          containers:
          - image: helloworld-v1
            ...
    ---
    apiVersion: extensions/v1beta1
    kind: Deployment
    metadata:
      name: helloworld-v2
    spec:
      replicas: 1
      template:
        metadata:
          labels:
            app: helloworld
            version: v2
        spec:
          containers:
          - image: helloworld-v2
            ...

    需要注意的是,这与使用普通 Kubernetes 进行 金丝雀部署的方式完全相同,但是在 Kubernetes 方式下控制流量分配需要调整每个 Deployment 的副本数目。例如,将 10% 的流量发送到金丝雀版本(v2),v1 和 v2 的副本可以分别设置为 9 和 1。

    但是在 启用 Istio的集群中,我们可以通过设置路由规则来控制流量分配。如将 10% 的流量发送到金丝雀版本本,我们可以使用 kubectl来设置以下的路由规则:

    $ kubectl apply -f - <<EOF
    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    metadata:
      name: helloworld
    spec:
      hosts:
        - helloworld
      http:
      - route:
        - destination:
            host: helloworld
            subset: v1
            weight: 90
        - destination:
            host: helloworld
            subset: v2
            weight: 10
    ---
    apiVersion: networking.istio.io/v1alpha3
    kind: DestinationRule
    metadata:
      name: helloworld
    spec:
      host: helloworld
      subsets:
      - name: v1
        labels:
          version: v1
      - name: v2
        labels:
          version: v2
    EOF

    当规则设置生效后,Istio 将确保只有 10% 的请求发送到金丝雀版本,无论每个版本的运行副本数量是多少。

    部署中的自动缩放

    由于我们不再需要保持副本比例,所以我们可以安全地设置 Kubernetes HPA来管理两个版本 Deployment 的副本:

    $ kubectl autoscale deployment helloworld-v1 --cpu-percent=50 --min=1 --max=10
    deployment "helloworld-v1" autoscaled
    $ kubectl autoscale deployment helloworld-v2 --cpu-percent=50 --min=1 --max=10
    deployment "helloworld-v2" autoscaled
    $ kubectl get hpa
    NAME           REFERENCE                 TARGET  CURRENT  MINPODS  MAXPODS  AGE
    Helloworld-v1  Deployment/helloworld-v1  50%     47%      1        10       17s
    Helloworld-v2  Deployment/helloworld-v2  50%     40%      1        10       15s

    如果现在对 helloworld服务上产生一些负载,我们会注意到,当扩容开始时, v1扩容副本数目远高于 v2,因为 v1pod 正在处理 90% 的负载。

    $ kubectl get pods | grep helloworld
    helloworld-v1-3523621687-3q5wh   0/2       Pending   0          15m
    helloworld-v1-3523621687-73642   2/2       Running   0          11m
    helloworld-v1-3523621687-7hs31   2/2       Running   0          19m
    helloworld-v1-3523621687-dt7n7   2/2       Running   0          50m
    helloworld-v1-3523621687-gdhq9   2/2       Running   0          11m
    helloworld-v1-3523621687-jxs4t   0/2       Pending   0          15m
    helloworld-v1-3523621687-l8rjn   2/2       Running   0          19m
    helloworld-v1-3523621687-wwddw   2/2       Running   0          15m
    helloworld-v1-3523621687-xlt26   0/2       Pending   0          19m
    helloworld-v2-4095161145-963wt   2/2       Running   0          50m

    如果更改路由规则将 50% 的流量发送到 v2,我们则可以在短暂的延迟后注意到 v1副本数的减少,而 v2副本数相应地增加。

    $ kubectl get pods | grep helloworld
    helloworld-v1-3523621687-73642   2/2       Running   0          35m
    helloworld-v1-3523621687-7hs31   2/2       Running   0          43m
    helloworld-v1-3523621687-dt7n7   2/2       Running   0          1h
    helloworld-v1-3523621687-gdhq9   2/2       Running   0          35m
    helloworld-v1-3523621687-l8rjn   2/2       Running   0          43m
    helloworld-v2-4095161145-57537   0/2       Pending   0          21m
    helloworld-v2-4095161145-9322m   2/2       Running   0          21m
    helloworld-v2-4095161145-963wt   2/2       Running   0          1h
    helloworld-v2-4095161145-c3dpj   0/2       Pending   0          21m
    helloworld-v2-4095161145-t2ccm   0/2       Pending   0          17m
    helloworld-v2-4095161145-v3v9n   0/2       Pending   0          13m

    最终结果与 Kubernetes Deployment 上线非常相似,只是整个流程并不是集中地进行编排和管理。相反,我们看到几个组件独立完成工作,虽然它们有因果关系。

    有一点不同的是,当我们停止负载时,无论设置路由规则如何,两个版本的副本数最终都会缩小到最小值(1)。

    $ kubectl get pods | grep helloworld
    helloworld-v1-3523621687-dt7n7   2/2       Running   0          1h
    helloworld-v2-4095161145-963wt   2/2       Running   0          1h

    聚焦金丝雀测试

    如上所述,Istio 路由规则可用于根据特定规则准进行流量路由,从而能够提供更复杂的金丝雀部署方案。例如,与简单通过将金丝雀版本暴露给任意百分比的用户方式不同,我们希望在内部用户上尝试,甚至可能只是内部用户的一部分。

    以下命令可将特定网站上 50% 的用户流量路由到金丝雀版本,而其他用户则不受影响:

    $ kubectl apply -f - <<EOF
    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    metadata:
      name: helloworld
    spec:
      hosts:
        - helloworld
      http:
      - match:
        - headers:
            cookie:
              regex: "^(.*?;)?(email=[^;]*@some-company-name.com)(;.*)?$"
        route:
        - destination:
            host: helloworld
            subset: v1
            weight: 50
        - destination:
            host: helloworld
            subset: v2
            weight: 50
      - route:
        - destination:
            host: helloworld
            subset: v1
    EOF

    和以前一样,绑定到 2 个版本 Deployment 的自动缩放器会相应地自动管理副本,但这对流量分配没有影响。

    总结

    本文中,我们看到了 Istio 如何支持通用可扩展的金丝雀部署,以及与 Kubernetes 部署的差异。Istio 服务网格提供了管理流量分配所需的基础控制,并完全独立于部署缩放。这允许简单而强大的方式来进行金丝雀测试和上线。

    支持金丝雀部署的智能路由只是 Istio 的众多功能之一,它将使基于大型微服务的应用程序的生产部署变得更加简单。查看 istio.io了解更多信息。

    可在 此处查看示例代码。

    盲盒是变相赌博OR智商税?

    $
    0
    0

    盲盒的起源

    盲盒顾名思义,就是看不见内容的盒子,其内部放置着不同的物品,消费者凭运气抽中商品。小小的盒子里装着不同样式的玩偶,在拆封之前永远不知道里面是什么,正是这种随机化的体验,让用户欲罢不能。

    盲盒营销最早可以追溯到明治末期的日本,其“前身”是日本百货公司在新年期间用来促销的福袋,福袋促销的方式通常用来作为商品尾货处理,用户既能获得趣味性体验,商家也可以成功清理库存。

    福袋的营销思路延续到了80年代的日本模型市场中,逐渐商品化,于是出现了“扭蛋机”线下机器。

    “扭蛋”与“福袋”大致相同,只是更集中在二次元、ACG等领域出现,销售的商品也大多是动漫IP手办、玩具模型、饰品挂件等。而盲盒营销在本土的早期应用,源于90年代中国开始的一系列“集卡式营销”。最为典型的代表案例就是小浣熊干脆面的水浒英雄卡。

    到了21世纪,盲盒的概念逐渐定型,之后,国内很少有人讨论盲盒,直到2016年泡泡玛特大力发展盲盒产品,才让盲盒营销逐渐风靡。如今,盲盒已经成熟应用在各种营销场景,成为IP玩具礼品、线上线下互动营销常用的方式之一,并深受Z世代的喜爱。

    泡泡玛特神话?

    泡泡玛特曾是一家新三板公司,2019年4月从新三板摘牌之际总市值约20亿人民币。2020年12月11日,泡泡玛特在香港上市,开盘涨幅100.26%,市值一度超1100亿港元。(备注:当前市值711亿人民币)。

    什么原因导致了一家公司在不到2年时间市值涨了35倍?是资本的炒作外还有什么内容支撑了其接近千亿的市值?伴随着好奇心一起研究下。

    收入来源:IP

    泡泡玛特的崛起,源于对潮流文化和内容的运作能力,并将授权IP(含独家授权)、自有IP应用于盲盒、手办、球关节娃娃及其他IP衍生品。其中自有IP“MOLLY”堪称最佳典范,生命力旺盛。根据年报,2020年8月上市的“MOLLY的一天”系列,截至2020年底单销售额破1亿元。包括自有IP、独家IP、非独家IP在内,自主IP已经成为泡泡玛特的收入核心来源,占2020年总收入的85%,相较于2019年有约3个百分点的提升。

    从数据看泡泡玛特过于依赖大IP,潜在的风险是一旦大IP受欢迎程度疲软,对公司业绩会有很大影响。另外可也看出爆款IP的打造是非常难的。爆款IP成功原因是什么,出IP外,怎么的系列主题才能大受欢迎?

    并不是每个IP均能获得成功,从泡泡玛特天猫官网我们可以看到在售的IP非常多,而真正被用户认可的也就那么3个。

    泡泡玛特能够有这么多的线下收入,主要原因是线下门店开的比较多(主要在一二线)。

    能够支持这么多门店的主要原因是门店的坪效较高。

    而支撑坪效的核心原因是毛利率非常的高。

    会员运营:高度粘性

    2019 年泡泡玛特的 220 万名注册会员的复购率达 58%,超过 45%的潮玩消费者每年在潮玩上的花费超过 500 元人民币。

    支撑起复购的主要原因或是是盲盒以12个为一套的设计方案。

    盲盒背后的逻辑

    间歇性强化

    强化(Reinforcement)

    强化(Reinforcement),是行为主义心理学中的一个重要概念,是关于理解和修正人的行为的一种学说。心理学研究发现,人类或动物为达到某种目的,会于所处的环境下采取特定行为;当这种行为带来的某种反应或后果对他有利时,这种行为就会在以后重复出现,而该结果就称为“强化物”;反之亦然,当其行为会对他带来不利时,这种行为就自然减弱或消失,个体对行为结果所产生的后续反应,就是以操作性条件反射进行的。而由于“强化物”的适时出现,增加了个体以后在相同情形下重复这种行为的概率,这表示“强化物”对于个体的反应起了强化作用。此种强化作用,即称之为“强化”。行为的强化有2种类型:

    • 正强化,也称积极强化、正向强化发生于一件渴求中的事或物作为一种结果而呈现,而这一结果刺激了这一渴求。在进行某个行为之后,增加对象喜爱的(通常是愉快的)刺激,并使该行为的出现频率增加。
    • 负强化,在进行某个行为之后,减少对象厌恶的(通常是不愉快的)刺激,并使该行为的出现频率增加。

    强化还可划分为一级强化和二级强化两类。

    • 一级强化满足人和动物的基本生理需要,如食物、水、安全、温暖、性等。
    • 二级强化是指任何一个中性刺激如果与一级强化反复联合,它就能获得自身的强化性质。包括社会强化(社会接纳、微笑)、信物(钱、级别、奖品等)和活动(自由地玩、听音乐、旅游等)。如金钱,对婴儿它不是强化物,但当小孩知道钱能换糖时,它就能对儿童的行为产生效果。再如分数,也是在受到教师的注意后才具有强化性质的。

    间歇性强化

    当动物所处环境中够多的变因被减少或是被控制时,他们在强化后的行为型态将明显的能够被预测。甚至当强化的速率适应于特定方法时,非常复杂的行为也能够被预测。强化程序是用来测定将被强化的反应(特定行为的单独出现)的计划。有两种极端情况,一种是连续强化,指强化所有反应;另一种是消弱,指没有反应被强化。

    • 固定比率强化(Fixed ratio schedule,简称FR),每固定次数反应都被强化,如每20个反应提供老鼠一个小食。例子:每月奖金制度。
    • 固定时距强化(Fixed interval schedule,简称FI),从训练开始或先前一个强化之后经过特定时间长度之后强化,假设在这段期间至少有一次反应出现。例子:按销量核算佣金收入。
    • 变动比率强化(Variable ratio schedule,简称VR),在不同的反应次数强化,有一个大约的平均值,例如老虎机不知道中奖机会。
    • 变动时距强化(Variable interval schedule,简称VI),在经过一段不固定的时间之后强化,有一个大约的平均值,并假设在这段期间至少有一次反应。例如查看邮箱,不知道何时会有重要的信件或垃圾邮件,因为无法预测,所以会产生一个稳定的检查行为。

    比率程序能够比间隔程序产生更高的反应频率。变化程序也比固定程序产生更高的反应频率。变化比率程序产生较高的反应频率,且对消弱有较大的抵抗力,赌博是变化比率程序最有代表性的例子。

    比起每做一个行为就有奖励,与持续性强化(continous reinforcement)不同的是,间歇性强化(intermittent)指每一个反应都不一定有后果。与赌徒上瘾的逻辑相同,因为透过不能确定何时有回报,周不时的回报会鼓励赌徒,就会令到该行为更难去消失 (resistant to extinction)。目标习惯了有时会没有奖励(赌徒则没有中奖)但不是永远没有机会(会嬴钱的)。换言之,持续性强化的效果虽然快,但是快来也快去,所习得的行为也很快。

    影响强化程序因素

    • 强化物的质与量:一般越多越容易强化某行为。一个刺激的效果也与成本效益有关,刺激的数量或是大小若是足够,对行为的刺激效果较大。例如特别庞大的乐透奖金,将使人愿意付出金钱和时间去购买彩券,如果奖金很少,可能不足以使人特地开车出门购买彩券。
    • 附带性:如果一个刺激并非总是伴随在行为之后,则刺激的效果将减少;如果一个刺激确实的附带在每一次行为之后,刺激的效果较大。
    • 强化延迟:个体进行一个行为过后,刺激回馈的立即性,会影响刺激的效果。行为与刺激的间隔愈短,效果愈大。例如一位在公路上超速的驾驶,若在一周之后才收到罚单,那么此罚单的效果将不如警察立即拦阻开罚单的效果。
    • 回应难度:如多容易就可以得到金钱,或若做好事做有多些分而获得小礼物,那做了坏事就扣分(response cost)。
    • 行为惯性(Behavioral momentum):当人们习惯了经常有回馈后,即使之后没有回馈仍会较大机会做该反应。
    • 规则化:一些已知的规则比起偶然发生的事难改变行为。
    • 强化历史:有过去经验而对比到未来会如何(behavioural contrast)。
    • 满足感,也可称为厌腻感:刺激的效果与动物个体对该刺激的胃口有关,个体对刺激的欲望愈大,刺激的效果也愈大;如果某动物个体已经对某个刺激感到满足或是厌腻,那么该刺激将不再有效果。

    其中立即性和附带性能够以神经化学来解释,当生物个体受到强化刺激,则大脑中的多巴胺通道将被活化,这些通道组成的网络释放短暂的多巴胺脉冲到许多树突,因此散发强化刺激讯号到突触后神经元。造成刚被活化的的突触对输出讯号的感应加强,因此造成强化刺激之前的行为的出现概率增加。在统计学上显示对行为的强化刺激成功。然而当强化刺激的立即性和附带性减少,多巴胺对突触的影响能力也会减少。

    好奇心的驱动

    人天生对不确定性的事物抱有恐惧感,因此会本能地追寻一个确定的答案:

    • 它究竟是什么?
    • 是好是坏?
    • 有没有危险?
    • 有什么好处?

    这种动机让人类对许多未知事物形成一种好奇心,希望去探索、了解,寻求确定的答案,最终获得掌控感。

    苹果手机每年召开新品发布会前,都会预先发布一个特别设计的邀请函,设计上似乎包含了发布会的新品内容暗示,但又并不明说,引发了业界的各种猜测和消费者的强烈好奇心,于是眼巴巴地等着发布会的到来。盲盒则光明正大地把“我就不告诉你”写在了脸上,直接告诉消费者,盒子里不知道是哪一款玩偶,你猜!意不意外?惊不惊喜?想知道就买回去!这种不确定性和随机性成为一种未知的惊喜,利用了消费者对商品的好奇心和期待,这是一种“情绪营销”。当我们怀着各种猜想和期待打开盲盒,看到里面那个小小的玩偶所引发的情绪波动,会在大脑中形成强烈而深刻的印象和记忆。这种感受是惊喜愉悦的,从而进一步强化了我们对商品的认同感和喜爱感,并促使我们以后进行复购,不断去反复获取和体验购买行为带来的愉悦感。

    成瘾性收集(收集心理)

    在我们周围,总有一些喜欢收藏的人,他们热衷于收藏各类古玩、字画、邮票、旧票据、杂志、书信等物品。不同的人,收藏的目的不同:

    • 情趣爱好:对生活有比较高的情趣追求,记录美好生活。
    • 精神寄托:寻找某种与世界的连接点,能够找到存在感。

    收藏成癖?:

    • 因极其缺乏某样东西、或失去过某样东西,因此渴望通过不断地购买、收藏来填补心灵上的“空缺感”“空虚感”
    • 人生阶段因为某些原因,造成了自身的心灵创伤,而收藏能给他们带来精神安慰、情感寄托(收藏哪些东西与其自身经历有关)
    • 成长过程中,因为一些偶然的机会,接触到了某些东西并对此“一见钟情”,通过收藏来满足自己的占有欲

    盲盒的营销思路主要是利用好奇心和收集欲望营销,消费者主要是小年轻,好奇心推动消费者首次尝试,收集欲望推动他们重复购买,不是新鲜模式了,早年的小浣熊等各种集卡、日本扭蛋都是这个模式,扭蛋和盲盒的做工、成品更漂亮更贵,收藏价值会比集卡高一些;在新顾客中,冲动消费的居多,很多人买过几个,热情就消退了,19年注册会员的复购率只有58%,可以说一半依靠老顾客的重复购买,一半靠新顾客尝试,收藏价值一般,所以咸鱼这种二手平台上的挂单量非常大,绝大多数不是因为增值了要交易赚钱,而是觉得不需要了、或者同款单品重复了,要交易掉。

    心理学上有个“禀赋效应”,就是当我们一旦拥有某项物品,我们对该物品价值的感知和评价就比未拥有之前大大提高,并且会越来越认同和喜爱。当人们把盲盒作为一种兴趣爱好来对待,为它投入大量的金钱、时间和精力,收集得越来越多之后,赋予它的价值和寄托的情感也会越来越大,也就越来越不容易舍弃。

    泡泡玛特的盲盒并非各自独立,而是按照IP分为多个系列,一个系列为一套。集齐一套12个。除常规款外还有第13个“隐藏款”,被抽到的概率大概为1/144。此外还有各种纪念款、联名款。也就是说,你永远都不知道你这次会买到哪一款。当人们购买或被赠送了第一个盲盒玩偶,就会不断的购买新的盲盒以期与其配套,当还差一两个隐藏款就能集齐全套时,更是不开盒就心痒难耐。这个聪明的设置很容易让人们陷入上瘾状态,强迫性地进行反复购买,希望收集齐全一整套玩偶。一家人就是要整整齐齐嘛!如果在一直抽不到想要的玩偶时就此收手,会感觉之前花费的时间和金钱似乎都成为了无法挽回的“沉没成本”,强烈的损失厌恶让人只有继续不断的投入,直到抽到一个隐藏款似乎才能弥补之前的损失,才感觉之前的投入是“值得”的。这也是为什么首次购买时会给予你各种新人优惠,目的就是想让你“一次入坑,越陷越深”。

    精神商品的满足

    一个盲盒的售卖价格大约50~80元,从成本来看,它并没有这么贵,我们更多的是为它的情绪价值买单。泡泡玛特联创人曾总结了盲盒的三点特征:

    • 没有世界观
    • 没有价值观
    • 艺术性不强

    这些特性也让人们能够没有认知负担地自由赋予盲盒各种情感价值,从中获得情感满足。可以说,盲盒除了满足人们的好奇心和收集癖,从深层心理因素来看,更是满足了当代年轻人的情感需求。这种情感需求是复杂而多样的,包括了安全感(减压)、社交、情感寄托等。玩盲盒成为一种完成任务的奖励和动力,满足了一些人的动机追求和内心需求,带来某种满足感。此外,随着玩盲盒人群的扩大、对玩偶款式收集的需求提升,盲盒也成为一种“社交货币”,成为进行社交的一个契机和驱动力,在年轻人中间形成独特的圈层。跟朋友一起时,如果大家都喜欢玩盲盒,自然会有更多的共同话题和情感联结。

    盲盒的游戏机制不仅可以带给用户刺激感,还会激发出买家后续的互动行为,例如大部分盲盒爱好者在天猫等购物平台、泡泡玛特线下零售店或无人贩卖机上购入盲盒,在闲鱼等二手交易平台上交换自己抽取的手办,在百度贴吧等社区中交流“改娃”经验,达到线上线下消费场景互通的效果。其实盲盒本身就是一款社交产品,是一种新生代的社交货币。在小红书或抖音等社交平台上关注抽盒博主、观看拆盒短视频,是与平台上其他对盲盒感兴趣的用户一起互动,通过评论区或其他互动形式构成一种社会临场感,同时产生情感镜像机制,具有虚拟代偿的功用。并且同好者之间会产生共情反应,出现情感投射效果,分享者获得好评与流量,而关注者会继续强化购买动机、继而实施购买行为。根据戈夫曼的前台/后台的情境,用户个人产出盲盒相关的UGC内容,有利于构建新的自我形象,通过在社交平台上晒娃、换娃的行为,可以帮助用户塑造出娃妈、潮流玩家、盲盒达人等多种虚拟社交形象。总体来说,上述消费者参与行为实现了由盲盒联结起来的用户交往去地域化和去中心化。

    赌徒和投机心态

    • 赌徒心态:拆盲盒的过程相当于买彩票,追求不确定性的刺激会导致玩家不断购买,从而成瘾。由于盲盒在拆开之前,消费者无法得知说购买的玩具款式,这种话信息的不确定性促使拆马哥的整个过程充满神秘感,另盲盒玩家感到兴奋和刺激,若抽中心仪的款式,这盲盒玩家心理上会等到额外惊喜,若未抽中心仪的款式,由于盲盒的单价较低,重复购买并不会对玩家造成较大的经济压力,此时笃定下次肯定抽中心仪款式的赌徒心态促使玩家再次购买盲盒。
    • 投机心态:此外,盲盒兴业存在二手市场,玩家一旦抽到重复的普通款盲盒,会将多余的款式放至闲鱼等二手买卖平台上流通销售。尤其是收藏孩子较高的隐藏款在盲盒二手市场上的溢价到达20-30倍,收到盲盒玩家热捧。部分盲盒玩家抱着投机的心态不断购买盲盒,并在二手平台上销售,从而赚取超额利润。

    赌徒谬误(Gambler’s Fallacy)是一种错误的推理方式,认为随机事件发生的机会率与之前发生的事件有关,即认为某个事件发生的概率,会随着之前没有发生该事件的次数而上升。在多次抽不到隐藏款时,人们会错误的认为下次抽中的概率增加,从而更加在沉没成本和翻本效应的影响下无法自拔。隐藏款因为其稀缺性具有极高的溢价,购买盲盒就像一次赌博,几十元的投入就可能获得上万元的收益,因此很多人在大量的投入无果之后,更期待“一次开盒,全局翻盘”。而赌徒谬误则加强了翻本效应对决策的影响。

    易得性启发

    易得性启发是指人们对于某个事件发生的概率的评估,往往会依据这个事件在知觉或记忆中的易得性程度,容易知觉或回想的事件被判定为更容易发生。朋友圈、微博、b站等众多平台都有各种盲盒套装分享、隐藏款开箱分享,抽到隐藏款不仅是获得了喜爱的玩偶,更成为了一种向其他人炫耀的资本。而这种炫耀性行为不仅对自身的购买行为进行了正强化,还刺激了他人的购买行为。

    实际上,大多数人只有在抽中了隐藏款、限量款时才会进行分享,而更多情况下的没有抽中的经历却因为没有被分享而被忽略,在这样的有偏信息来源下,我们会错误的认为似乎抽中隐藏款并没有那么难,而忽略了本身的概率,产生易得性启发式这一认知偏差,掉入消费陷阱。

    盲盒背后的风险

    随着泡泡玛特的流行,越来越多的商家也参与进入“盲盒”经济。包括玩偶盲盒、服装盲盒、鞋子盲盒、文具盲盒、键盘盲盒、美妆盲盒、饮料盲盒、机票盲盒、酒店盲盒…如果你也想加入,就需要考虑以下因素。

    《规范促销行为暂行规定》已于2020年1月15日经国家市场监督管理总局2020年第1次局务会议审议通过,现予公布,自2020年12月1日起施行。

    第三章 有奖销售行为规范

     

    第十一条 本规定所称有奖销售,是指经营者以销售商品或者获取竞争优势为目的,向消费者提供奖金、物品或者其他利益的行为,包括抽奖式和附赠式等有奖销售。

    抽奖式有奖销售是指经营者以抽签、摇号、游戏等带有偶然性或者不确定性的方法,决定消费者是否中奖的有奖销售行为。

    附赠式有奖销售是指经营者向满足一定条件的消费者提供奖金、物品或者其他利益的有奖销售行为。

    经政府或者政府有关部门依法批准的有奖募捐及其他彩票发售活动,不适用本规定。

    第十二条 经营者为了推广移动客户端、招揽客户、提高知名度、获取流量、提高点击率等,附带性地提供物品、奖金或者其他利益的行为,属于本规定所称的有奖销售。

    第十三条 经营者在有奖销售前,应当明确公布奖项种类、参与条件、参与方式、开奖时间、开奖方式、奖金金额或者奖品价格、奖品品名、奖品种类、奖品数量或者中奖概率、兑奖时间、兑奖条件、兑奖方式、奖品交付方式、弃奖条件、主办方及其联系方式等信息,不得变更,不得附加条件,不得影响兑奖,但有利于消费者的除外。

    在现场即时开奖的有奖销售活动中,对超过五百元奖项的兑奖情况,应当随时公示。

    第十四条 奖品为积分、礼券、兑换券、代金券等形式的,应当公布兑换规则、使用范围、有效期限以及其他限制性条件等详细内容;需要向其他经营者兑换的,应当公布其他经营者的名称、兑换地点或者兑换途径。

    第十五条 经营者进行有奖销售,不得采用以下谎称有奖的方式:

    (一)虚构奖项、奖品、奖金金额等;

    (二)仅在活动范围中的特定区域投放奖品;

    (三)在活动期间将带有中奖标志的商品、奖券不投放、未全部投放市场;

    (四)将带有不同奖金金额或者奖品标志的商品、奖券按不同时间投放市场;

    (五)未按照向消费者明示的信息兑奖;

    (六)其他谎称有奖的方式。

    第十六条 经营者进行有奖销售,不得采用让内部员工、指定单位或者个人中奖等故意让内定人员中奖的欺骗方式。

    第十七条 抽奖式有奖销售最高奖的金额不得超过五万元。有下列情形之一的,认定为最高奖的金额超过五万元:

    (一)最高奖设置多个中奖者的,其中任意一个中奖者的最高奖金额超过五万元;

    (二)同一奖券或者购买一次商品具有两次或者两次以上获奖机会的,累计金额超过五万元;

    (三)以物品使用权、服务等形式作为奖品的,该物品使用权、服务等的市场价格超过五万元;

    (四)以游戏装备、账户等网络虚拟物品作为奖品的,该物品市场价格超过五万元;

    (五)以降价、优惠、打折等方式作为奖品的,降价、优惠、打折等利益折算价格超过五万元;

    (六)以彩票、抽奖券等作为奖品的,该彩票、抽奖券可能的最高奖金额超过五万元;

    (七)以提供就业机会、聘为顾问等名义,并以给付薪金等方式设置奖励,最高奖的金额超过五万元;

    (八)以其他形式进行抽奖式有奖销售,最高奖金额超过五万元。

    第十八条 经营者以非现金形式的物品或者其他利益作为奖品的,按照同期市场同类商品的价格计算其金额。

    第十九条 经营者应当建立档案,如实、准确、完整地记录设奖规则、公示信息、兑奖结果、获奖人员等内容,妥善保存两年并依法接受监督检查。

    盲盒与实物彩票的边界越来越模糊,也必定会引起相关部门的关注。未来可能的监管方案:

    • 盲盒产品开出来的所有的产品要相同(不允许隐藏款存在—人为制造稀缺)
    • 任何盲盒类产品可单独指定购买,售价不允许超过盲盒单价的X%(比如100%)

    参考链接:

    如何利用Rancher和Kong实现服务网格?

    $
    0
    0

    服务网格(Service mesh)是当前新兴的架构模式,越来越受到人们的青睐。与Kubernetes一起,服务网格可以形成一个强大的平台,它可以解决在微服务集群或服务基础设施上发现的高度分布式环境中出现的技术需求。服务网格是一个专门的基础设施层,用于促进微服务之间的服务到服务通信。

    服务网格解决了基于微服务的应用中典型的通信需求,包括加密隧道、健康检查、断路器、负载均衡以及流量许可。如果离开微服务来解决这些需求,会导致开发过程中产生高昂的费用和耗时。

    在本文中,我们将对服务网格架构模式解决的最常见的微服务通信需求进行概述。

    微服务动态和内在挑战

    当你意识到微服务实现了相当多的与最初分配给它们的业务逻辑无关的代码时,问题就出现了。此外,有可能你有多个微服务在非标准化的流程中实现了类似的功能。换句话说,微服务开发团队应该专注于业务逻辑,并将低级通信能力留给特定的层。

    继续推进我们的方案,需要考虑微服务的内在动态。在给定的时间内,你可能由于以下几个原因而拥有一个微服务的多个实例:
    • 吞吐量(Throughput):根据传入的请求,你可能拥有更多或更少的微服务实例
    • 金丝雀发布
    • 蓝绿部署
    • A/B测试


    简而言之,微服务到微服务的通信有特定的需求和问题需要解决。以下图片展示了这一方案:



    该图示描述了几个技术挑战。显然,Microservice 1的主要职责是均衡所有Microservice 2实例之间的负载。因此,Microservice 1必须弄清楚我们在请求时刻有多少个Microservice 2实例。换句话说,Microservice 1必须实现服务发现和负载均衡。

    另一方面,Microservice 2必须实现一些服务注册功能以告知Microservice 1何时有全新的实例。

    想要拥有一个完全动态的环境,以下这些功能应该是微服务开发的一部分:
    • 流量控制:负载均衡的自然演变。我们想指定应该发送到每个Microservice 2实例的请求数量。 在Microservice
      1和2之间加密通信
    • 借助断路器和健康检查以解决和克服网络问题


    总而言之,主要问题是开发团队花费了大量资源编写十分复杂的代码,而这些代码与微服务预期交付的业务逻辑不直接相关。

    有潜力的解决方案

    如何将所有微服务都可以调用的外部标准化组件中的所有非功能和操作功能外部化?例如,下图编译了所有功能,这些功能不属于给定的微服务。因此,在确定所有功能之后,我们需要决定在哪里实现它们。

    在这里插入图片描述

    Solution #1 :将所有功能封装在一个library中

    开发者将负责调用library提供的函数来解决微服务通信需求。

    这个解决方案有几个缺点:

    这是一个紧密耦合的解决方案,意味着微服务高度依赖于library
    这个模式对于分布和升级新版本的library来说并不容易
    这不符合微服务多语言的原则,因为这会将不同的编程语言应用于不同的上下文。

    Solution #2:透明代理(Transparent Proxy)

    在这里插入图片描述

    这个解决方案实现了同样的功能集合。但是,采用了一种非常不同的方法:每个微服务都有一个特定的组件,扮演代理的角色,负责处理它的传入和传出流量。代理解决了我们之前描述的库的缺点,具体如下:
    • 代理是透明的,这意味着微服务不会意识到它正在附近运行并实现了与其他微服务进行通信所需的所有功能。
    • 由于它是一个透明的代理,开发者不需要改变代码来引用代理。因此,从微服务开发的角度来看,升级代理将是一个并不会对开发流程造成太大影响。
    • 代理可以使用微服务使用的不同技术和编程语言进行开发。


    服务网格架构模式

    虽然透明代理的方法给微服务开发团队和微服务通信需求带来了一些好处,但仍有一些缺失的部分:
    • 代理只是执行策略来实现通信需求,例如负载均衡、金丝雀发布等。
    • 由什么来负责定义这样的策略,并在所有运行的代理上发布呢?


    解决方案架构需要另一个组件,这些组件将被管理员用来定义策略,它将负责向代理传播策略。

    以下图片展示了最终架构,也就是服务网格模式:

    在这里插入图片描述

    如你所见,该模式包含了我们所描述的两个主要组件。
    • 数据平面:也被称为sidecar,它扮演着透明代理的角色。同样,每个微服务都会有自己的数据平面,拦截所有的入站和出站流量,并应用之前描述的策略。
    • 控制平面:由管理员用来定义策略并发布到数据平面。


    一些重要的事情需要注意:
    • 这是一个 "push-based "的架构。数据平面不做 "调用 "来获取策略——那将会消耗网络。
    • 数据平面通常向控制平面或特定的基础设施报告使用指标。


    手把手教你使用Rancher、Kong和Kong Mesh

    Kong提供了一个企业级的综合服务连接平台,其包括了API gateway、Kubernetes ingress controller以及服务网格实现。该平台允许用户部署多个环境,如本地、混合云、多区域以及多云环境。

    让我们借助运行在独立于云架构(cloud-agnostic)的Kubernetes集群上的金丝雀发布来实现服务网格,该集群可能包括GKE集群或任何其他的Kubernetes发行版。服务网格将由Kong Mesh实现,并由Kong for Kubernetes作为Kubernetes Ingress Controller。一般而言,ingress controller负责定义进入你的Kubernetes集群的入口点,暴露部署在其内部的微服务,并对其实行消费策略。

    首先,确保你已经安装Rancher以及正在运行一个由Rancher管理的Kubernetes集群。在登录到Rancher之后,选在我们将要使用的Kubernetes集群,在本例中为“kong-rancher”。点击Cluster Explorer。你将会重定向到以下页面:

    在这里插入图片描述

    现在,让我们从服务网格开始:

    1、 Kong Mesh Helm Chart

    回到Rancher Cluster Manger主页并再次选择你的集群。点击菜单栏的“Tools”选项然后点击Catalogs,以创建一个新的catalog。点击Add Catalog按钮,将Kong Mesh的Helm chart收录其中( https://kong.github.io/kong-mesh-charts/)。

    选择Global作为范围,Helm v3作为Helm版本。

    在这里插入图片描述

    现在点击Apps和Launch来查看在Catalog中可用的Kong Mesh。请注意,Kong作为Rancher的合作伙伴默认提供了Kong for Kubernetes的Helm chart:

    在这里插入图片描述

    2、 安装Kong Mesh

    点击顶部菜单栏Namespaces选项并创建一个“kong-mesh-system”命名空间。

    在这里插入图片描述

    将鼠标移到kong-rancher顶部菜单选项上,点击kong-rancher活动集群。

    在这里插入图片描述

    点击Launch kubetcl

    在这里插入图片描述

    创建一个名为“license.json”的文件,用于存放你从Kong Mesh收到的license。格式如下:

    {“license”:  
    {“version”:1,“signature”:“6a7c81af4b0a42b380be25c2816a2bb1d761c0f906ae884f93eeca1fd16c8b5107cb6997c958f45d247078ca50a25399a5f87d546e59ea3be28284c3075a9769”,“payload”:
    {“customer”:“Kong_SE_Demo_H1FY22”,“license_creation_date”:“2020-11-30”,“product_subscription”:“Kong Enterprise Edition”,“support_plan”:“None”,“admin_seats”:“5”,“dataplanes”:“5”,“license_expiration_date”:“2021-06-30”,“license_key”:“XXXXXXXXXXXXX”
    }}}

    现在使用以下命令创建一个Kubernetes通用密钥:
    kubectl create secret generic kong-mesh-license -n kong-mesh-system --from-file=./license.json  


    关闭kubectl会话,点击Default项目以及顶部菜单栏的Apps。点击Launch按钮并选择kong-mesh Helm chart。

    点击Use an existing namespace并选择我们刚刚创建的那个。这有几个参数( https://artifacthub.io/package ... g-mesh)来配置Kong Mesh,但我们将保留所有默认值。点击Launch之后,你应该看到Kong Mesh应用程序部署完成。

    在这里插入图片描述

    你可以再次使用Rancher Cluster Explorer来检查安装。点击左侧菜单的Pods并选择kong-mesh-system的命名空间。

    你也可以像这样使用kubectl:
    NAMESPACE          NAME                                                      READY   STATUS    RESTARTS   AGE  
    cattle-system      cattle-cluster-agent-785fd5f54d-r7x8r                     1/1     Running   0          75m
    fleet-system       fleet-agent-77c78f9c74-f97tv                              1/1     Running   0          75m
    kong-mesh-system   kuma-control-plane-5b9c6f4598-nvq8q                       1/1     Running   0          16m
    kube-system        event-exporter-gke-666b7ffbf7-n9lfl                       2/2     Running   0          76m
    kube-system        fluentbit-gke-xqsdv                                       2/2     Running   0          76m
    kube-system        gke-metrics-agent-gjrqr                                   1/1     Running   0          76m
    kube-system        konnectivity-agent-4c4hf                                  1/1     Running   0          76m
    kube-system        kube-dns-66d6b7c877-tq877                                 4/4     Running   0          76m
    kube-system        kube-dns-autoscaler-5c78d65cd9-5hcxs                      1/1     Running   0          76m
    kube-system        kube-proxy-gke-c-kpwnf-default-0-be059c1c-49qp            1/1     Running   0          76m
    kube-system        l7-default-backend-5b76b455d-v6dvg                        1/1     Running   0          76m
    kube-system        metrics-server-v0.3.6-547dc87f5f-qntjf                    2/2     Running   0          75m
    kube-system        prometheus-to-sd-fdf9j                                    1/1     Running   0          76m
    kube-system        stackdriver-metadata-agent-cluster-level-68d94db6-64n4r   2/2     Running   1          75m


    3、 微服务部署

    我们的Service Mesh部署是基于一个简单的微服务到微服务的通信场景。由于我们运行的是金丝雀发布,被调用的微服务有两个版本:

    “magnanimo”:通过Kong暴露Kubernetes ingress controller。
    “benigno”:提供了一个 “hello” endpoint,在这个端点中,它呼应了当前的datetime。它有一个金丝雀发布,会发送一个稍微不同的响应。

    下图展示了这一架构:

    在这里插入图片描述

    创建一个带有sidecar注入注释的命名空间。你可以再次使用Rancher Cluster Manager:选择你的集群,然后单击Projects/Namespaces。点击Add Namespace。输入 “kong-mesh-app” 作为名称,并包含一个带有 “kuma.io/sidecar-injection” 键和 “enabled” 作为其值的注释。

    在这里插入图片描述

    当然,你也可以选择使用kubectl
    kubectl create namespace kong-mesh-app  

    kubectl annotate namespace kong-mesh-app kuma.io/sidecar-injection=enabled

    Submit the following declaration to deploy Magnanimo injecting the Kong Mesh data plane

    cat <
    apiVersion: apps/v1

    kind: Deployment

    metadata:

    name: magnanimo

    namespace: kong-mesh-app

    spec:

    replicas: 1

    selector:

    matchLabels:

    app: magnanimo

    template:

    metadata:

    labels:

    app: magnanimo

    spec:

    containers:
    • name: magnanimo

    image: claudioacquaviva/magnanimo

    ports:
    • containerPort: 4000

    ---

    apiVersion: v1

    kind: Service

    metadata:

    name: magnanimo

    namespace: kong-mesh-app

    labels:

    app: magnanimo

    spec:

    type: ClusterIP

    ports:
    • port: 4000

    name: http

    selector:

    app: magnanimo

    EOF


    使用Rancher Cluster Manager检查你的部署。将鼠标移动至kong-rancher菜单上,点击Default项目,可以看到当前的部署情况:

    在这里插入图片描述

    点击magnanimo检查部署的细节,包括其pods:

    在这里插入图片描述

    点击magnanimo pod,检查其内部运行的容器。

    在这里插入图片描述

    我们可以看到,pod有两个运行的容器:
    • magnanimo:微服务实际运行的地方。
    • kuma-sidecar:在部署的时候注入,作为Kong Mesh的数据平面。


    同理,部署Benigno的时候,也有自己的sidecar:
    cat <
    apiVersion: apps/v1

    kind: Deployment

    metadata:

    name: benigno-v1

    namespace: kong-mesh-app

    spec:

    replicas: 1

    selector:

    matchLabels:

    app: benigno

    template:

    metadata:

    labels:

    app: benigno

    version: v1

    spec:

    containers:
    • name: benigno

    image: claudioacquaviva/benigno

    ports:
    • containerPort: 5000

    ---

    apiVersion: v1

    kind: Service

    metadata:

    name: benigno

    namespace: kong-mesh-app

    labels:

    app: benigno

    spec:

    type: ClusterIP

    ports:
    • port: 5000

    name: http

    selector:

    app: benigno

    EOF

    And finally, deploy Benigno canary release. Notice that the canary release will be abstracted by the same Benigno Kubernetes Service created before:

    cat <
    apiVersion: apps/v1

    kind: Deployment

    metadata:

    name: benigno-v2

    namespace: kong-mesh-app

    spec:

    replicas: 1

    selector:

    matchLabels:

    app: benigno

    template:

    metadata:

    labels:

    app: benigno

    version: v2

    spec:

    containers:
    • name: benigno

    image: claudioacquaviva/benigno\_rc

    ports:
    • containerPort: 5000

    EOF


    使用以下命令检查部署和Pods:

    {{{$ kubectl get pod --all-namespaces  
    NAMESPACE          NAME                                                      READY   STATUS    RESTARTS   AGE
    cattle-system      cattle-cluster-agent-785fd5f54d-r7x8r                     1/1     Running   0          75m
    fleet-system       fleet-agent-77c78f9c74-f97tv                              1/1     Running   0          75m
    kong-mesh-app      benigno-v1-fd4567d95-drnxq                                2/2     Running   0          110s
    kong-mesh-app      benigno-v2-b977c867b-lpjpw                                2/2     Running   0          30s
    kong-mesh-app      magnanimo-658b67fb9b-tzsjp                                2/2     Running   0          5m3s
    kong-mesh-system   kuma-control-plane-5b9c6f4598-nvq8q                       1/1     Running   0          16m
    kube-system        event-exporter-gke-666b7ffbf7-n9lfl                       2/2     Running   0          76m
    kube-system        fluentbit-gke-xqsdv                                       2/2     Running   0          76m
    kube-system        gke-metrics-agent-gjrqr                                   1/1     Running   0          76m
    kube-system        konnectivity-agent-4c4hf                                  1/1     Running   0          76m
    kube-system        kube-dns-66d6b7c877-tq877                                 4/4     Running   0          76m
    kube-system        kube-dns-autoscaler-5c78d65cd9-5hcxs                      1/1     Running   0          76m
    kube-system        kube-proxy-gke-c-kpwnf-default-0-be059c1c-49qp            1/1     Running   0          76m
    kube-system        l7-default-backend-5b76b455d-v6dvg                        1/1     Running   0          76m
    kube-system        metrics-server-v0.3.6-547dc87f5f-qntjf                    2/2     Running   0          75m
    kube-system        prometheus-to-sd-fdf9j                                    1/1     Running   0          76m
    kube-system        stackdriver-metadata-agent-cluster-level-68d94db6-64n4r   2/2     Running   1          75m


    $ kubectl get service --all-namespaces
    NAMESPACE          NAME                   TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)                                                AGE
    default            kubernetes             ClusterIP   10.0.16.1             443/TCP                                                79m
    kong-mesh-app      benigno                ClusterIP   10.0.20.52            5000/TCP                                               4m6s
    kong-mesh-app      magnanimo              ClusterIP   10.0.30.251           4000/TCP                                               7m18s
    kong-mesh-system   kuma-control-plane     ClusterIP   10.0.21.228           5681/TCP,5682/TCP,443/TCP,5676/TCP,5678/TCP,5653/UDP   18m
    kube-system        default-http-backend   NodePort    10.0.19.10            80:32296/TCP                                           79m
    kube-system        kube-dns               ClusterIP   10.0.16.10            53/UDP,53/TCP                                          79m
    kube-system        metrics-server         ClusterIP   10.0.20.174           443/TCP                                                79m
    }}}

    你也可以使用Kong Mesh控制台来检查微服务和数据平面。在Terminal上运行以下命令:
    kubectl port-forward service/kuma-control-plane -n kong-mesh-system 5681  


    重定向你的浏览器到 http://localhost:5681/gui。点击Skip to Dashboard和All Data Plane Proxies:

    在这里插入图片描述

    启动一个循环,看看金丝雀发布的运行情况。注意服务已经被部署为ClusterIP类型,所以你需要用 “port-forward”直接暴露它们。下一步将展示如何用Ingress Controller暴露服务。

    在本地terminal上运行:
    kubectl port-forward service/magnanimo -n kong-mesh-app 4000  


    打开另一个Terminal,开始循环。请求要到Magnanimo提供的4000端口。路径“/hw2 ”将请求路由到Benigno服务,它后面有两个endpoint,与Benigno两个版本有关:
    while [1]; do curl http://localhost:4000/hw2; echo; done  


    你应该看到类似下方的结果:
    Hello World, Benigno: 2020-11-20 12:57:05.811667  
    Hello World, Benigno: 2020-11-20 12:57:06.304731
    Hello World, Benigno, Canary Release: 2020-11-20 12:57:06.789208
    Hello World, Benigno: 2020-11-20 12:57:07.269674
    Hello World, Benigno, Canary Release: 2020-11-20 12:57:07.755884
    Hello World, Benigno, Canary Release: 2020-11-20 12:57:08.240453
    Hello World, Benigno: 2020-11-20 12:57:08.728465
    Hello World, Benigno: 2020-11-20 12:57:09.208588
    Hello World, Benigno, Canary Release: 2020-11-20 12:57:09.689478
    Hello World, Benigno, Canary Release: 2020-11-20 12:57:10.179551
    Hello World, Benigno: 2020-11-20 12:57:10.662465
    Hello World, Benigno: 2020-11-20 12:57:11.145237
    Hello World, Benigno, Canary Release: 2020-11-20 12:57:11.618557
    Hello World, Benigno: 2020-11-20 12:57:12.108586
    Hello World, Benigno, Canary Release: 2020-11-20 12:57:12.596296
    Hello World, Benigno, Canary Release: 2020-11-20 12:57:13.093329
    Hello World, Benigno: 2020-11-20 12:57:13.593487
    Hello World, Benigno, Canary Release: 2020-11-20 12:57:14.068870


    4、 控制金丝雀发布的成本

    正如我们所见,两个Benigno微服务发布的请求使用了循环策略。也就是说,我们无法控制金丝雀发布的花销。Service Mesh允许我们定义何时以及如何将金丝雀发布暴露给我们的consumer(在本例中指Magnanimo微服务)。

    要定义一个策略来控制流向两个版本的流量,需要使用下面这个声明。它说90%的流量应该流向当前版本,而只有10%的流量应该重定向到金丝雀发布。
    cat <
    apiVersion: kuma.io/v1alpha1  
    kind: TrafficRoute
    mesh: default
    metadata:
    namespace: default
    name: route-1
    spec:
    sources:
    - match:
    kuma.io/service: magnanimo\_kong-mesh-app\_svc\_4000
    destinations:
    - match:
    kuma.io/service: benigno\_kong-mesh-app\_svc\_5000
    conf:
    split:
    - weight: 90
    destination:
    kuma.io/service: benigno\_kong-mesh-app\_svc\_5000
    version: v1
    - weight: 10
    destination:
    kuma.io/service: benigno\_kong-mesh-app\_svc\_5000
    version: v2
    EOF


    应用声明之后,你应该看到如下结果:
    Hello World, Benigno: 2020-11-20 13:05:02.553389  
    Hello World, Benigno: 2020-11-20 13:05:03.041120
    Hello World, Benigno: 2020-11-20 13:05:03.532701
    Hello World, Benigno: 2020-11-20 13:05:04.021804
    Hello World, Benigno: 2020-11-20 13:05:04.515245
    Hello World, Benigno, Canary Release: 2020-11-20 13:05:05.000644
    Hello World, Benigno: 2020-11-20 13:05:05.482606
    Hello World, Benigno: 2020-11-20 13:05:05.963663
    Hello World, Benigno, Canary Release: 2020-11-20 13:05:06.446599
    Hello World, Benigno: 2020-11-20 13:05:06.926737
    Hello World, Benigno: 2020-11-20 13:05:07.410605
    Hello World, Benigno: 2020-11-20 13:05:07.890827
    Hello World, Benigno: 2020-11-20 13:05:08.374686
    Hello World, Benigno: 2020-11-20 13:05:08.857266
    Hello World, Benigno: 2020-11-20 13:05:09.337360
    Hello World, Benigno: 2020-11-20 13:05:09.816912
    Hello World, Benigno: 2020-11-20 13:05:10.301863
    Hello World, Benigno: 2020-11-20 13:05:10.782395
    Hello World, Benigno: 2020-11-20 13:05:11.262624
    Hello World, Benigno: 2020-11-20 13:05:11.743427
    Hello World, Benigno: 2020-11-20 13:05:12.221174
    Hello World, Benigno: 2020-11-20 13:05:12.705731
    Hello World, Benigno: 2020-11-20 13:05:13.196664
    Hello World, Benigno: 2020-11-20 13:05:13.680319


    5、 安装Kong for Kubernetes

    让我们回到Rancher中安装我们的Kong for Kubernetes Ingress Controller,并控制服务网格的暴露。在Rancher Catalog页面中,点击Kong图标。接受默认值,然后点击Launch:

    在这里插入图片描述

    你应该看到Kong和Kong Mesh这两个应用程序都已经部署完成:

    图片

    在这里插入图片描述

    再次使用kubectl检查安装:
    $ kubectl get pod --all-namespaces  
    NAMESPACE          NAME                                                      READY   STATUS    RESTARTS   AGE
    cattle-system      cattle-cluster-agent-785fd5f54d-r7x8r                     1/1     Running   0          84m
    fleet-system       fleet-agent-77c78f9c74-f97tv                              1/1     Running   0          83m
    kong-mesh-app      benigno-v1-fd4567d95-drnxq                                2/2     Running   0          10m
    kong-mesh-app      benigno-v2-b977c867b-lpjpw                                2/2     Running   0          8m47s
    kong-mesh-app      magnanimo-658b67fb9b-tzsjp                                2/2     Running   0          13m
    kong-mesh-system   kuma-control-plane-5b9c6f4598-nvq8q                       1/1     Running   0          24m
    kong               kong-kong-754cd6947-db2j9                                 2/2     Running   1          72s
    kube-system        event-exporter-gke-666b7ffbf7-n9lfl                       2/2     Running   0          85m
    kube-system        fluentbit-gke-xqsdv                                       2/2     Running   0          84m
    kube-system        gke-metrics-agent-gjrqr                                   1/1     Running   0          84m
    kube-system        konnectivity-agent-4c4hf                                  1/1     Running   0          84m
    kube-system        kube-dns-66d6b7c877-tq877                                 4/4     Running   0          84m
    kube-system        kube-dns-autoscaler-5c78d65cd9-5hcxs                      1/1     Running   0          84m
    kube-system        kube-proxy-gke-c-kpwnf-default-0-be059c1c-49qp            1/1     Running   0          84m
    kube-system        l7-default-backend-5b76b455d-v6dvg                        1/1     Running   0          85m
    kube-system        metrics-server-v0.3.6-547dc87f5f-qntjf                    2/2     Running   0          84m
    kube-system        prometheus-to-sd-fdf9j                                    1/1     Running   0          84m
    kube-system        stackdriver-metadata-agent-cluster-level-68d94db6-64n4r   2/2     Running   1          84m


    $ kubectl get service --all-namespaces
    NAMESPACE          NAME                   TYPE           CLUSTER-IP    EXTERNAL-IP     PORT(S)                                                AGE
    default            kubernetes             ClusterIP      10.0.16.1               443/TCP                                                85m
    kong-mesh-app      benigno                ClusterIP      10.0.20.52              5000/TCP                                               10m
    kong-mesh-app      magnanimo              ClusterIP      10.0.30.251             4000/TCP                                               13m
    kong-mesh-system   kuma-control-plane     ClusterIP      10.0.21.228             5681/TCP,5682/TCP,443/TCP,5676/TCP,5678/TCP,5653/UDP   24m
    kong               kong-kong-proxy        LoadBalancer   10.0.26.38    35.222.91.194   80:31867/TCP,443:31039/TCP                             78s
    kube-system        default-http-backend   NodePort       10.0.19.10              80:32296/TCP                                           85m
    kube-system        kube-dns               ClusterIP      10.0.16.10              53/UDP,53/TCP                                          85m
    kube-system        metrics-server         ClusterIP      10.0.20.174             443/TCP                                                85m


    6、 创建Ingress

    通过下面的声明,我们将通过一个Ingress和它的路由 “/route1” 来暴露Magnanimo微服务。
    cat <
    apiVersion: extensions/v1beta1  
    kind: Ingress
    metadata:
    name: route1
    namespace: kong-mesh-app
    annotations:
    konghq.com/strip-path: "true"
    spec:
    rules:
    - http:
    paths:
    - path: /route1
    backend:
    serviceName: magnanimo
    servicePort: 4000
    EOF


    现在,临时的 “port-forward” 暴露机制可以被正式的Ingress所取代。而我们的循环也可以开始消耗Ingress,结果如下:
    {while [1]; do curl http://35.222.91.194/route1/hw2; echo; done  

    记一次K8s排错实战

    $
    0
    0

    这是我参与更文挑战的第3天,活动详情查看: 更文挑战

    一 背景

    收到测试环境集群告警,登陆K8s集群进行排查。

    二 故障定位

    2.1 查看pod

    查看kube-system node2节点calico pod异常

    • 查看详细信息,查看node2节点没有存储空间,cgroup泄露

    2.2 查看存储

    • 登陆node2查看服务器存储信息,目前空间还很充足

    • 集群使用到的分布式存储为ceph,因此查看ceph集群状态

    三 操作

    3.1 ceph修复

    目前查看到ceph集群异常,可能导致node2节点cgroup泄露异常,进行手动修复ceph集群。

    数据的不一致性(inconsistent)指对象的大小不正确、恢复结束后某副本出现了对象丢失的情况。数据的不一致性会导致清理失败(scrub error)。
    CEPH在存储的过程中,由于特殊原因,可能遇到对象信息大小和物理磁盘上实际大小数据不一致的情况,这也会导致清理失败。
    复制代码

    由图可知,pg编号1.7c 存在问题,进行修复。

    • pg修复
    ceph pg repair 1.7c
    复制代码

    • 进行修复后,稍等一会,再次进行查看,ceph集群已经修复

    3.2 进行pod修复

    对异常pod进行删除,由于有控制器,会重新拉起最新的pod

    查看pod还是和之前一样,分析可能由于ceph异常,导致node2节点cgroup泄露,网上检索重新编译

    1. Google一番后发现与 github.com/rootsongjc/…这个同学的问题基本一致。 存在的可能有,
    • Kubelet 宿主机的 Linux 内核过低 - Linux version 3.10.0-862.el7.x86_64
    • 可以通过禁用kmem解决

    查看系统内核却是低版本

    3.3 故障再次定位

    最后,因为在启动容器的时候runc的逻辑会默认打开容器的kmem accounting,导致3.10内核可能的泄漏问题

    在此需要对no space left的服务器进行 reboot重启,即可解决问题,出现问题的可能为段时间内删除大量的pod所致。

    初步思路,可以在今后的集群管理汇总,对服务器进行维修,通过删除节点,并对节点进行reboot处理

    3.4 对node2节点进行维护

    3.4.1 标记node2为不可调度

    kubectl cordon node02
    复制代码

    3.4.2 驱逐node2节点上的pod

    kubectl drain node02 --delete-local-data --ignore-daemonsets --force
    复制代码
    • --delete-local-data 删除本地数据,即使emptyDir也将删除;
    • --ignore-daemonsets 忽略DeamonSet,否则DeamonSet被删除后,仍会自动重建;
    • --force 不加force参数只会删除该node节点上的ReplicationController, ReplicaSet, DaemonSet,StatefulSet or Job,加上后所有pod都将删除;

    目前查看基本node2的pod均已剔除完毕

    此时与默认迁移不同的是,pod会 先重建再终止,此时的 服务中断时间=重建时间+服务启动时间+readiness探针检测正常时间,必须等到 1/1 Running服务才会正常。 因此在单副本时迁移时,服务终端是不可避免的

    3.4.3 对node02进行重启

    重启后node02已经修复完成。

    对node02进行恢复

    • 恢复node02可以正常调度
    kubectl uncordon node02
    复制代码

    四 反思

    • 后期可以对部署k8s 集群内核进行升级。
    • 集群内可能pod的异常,由于底层存储或者其他原因导致,需要具体定位到问题进行针对性修复。

    参考链接

    数据库内核的快照技术实现原理

    $
    0
    0

    "快照(Snapshot)"是数据库领域非常重要的一个概念, 最初是用于数据备份. 如今, 快照技术已经成为数据库内核(引擎)最核心的技术特性之一. 数据库内核的绝大多数操作, 都依赖于快照, 例如, LevelDB的每一次读取操作和遍历操作, 其内部都必须创建一个快照, 所以, 对于一个请求量非常大的系统, 数据库内核每秒种就要创建和销毁几十万次快照. 因此, 如何快速地创建和销毁快照, 成为一个数据库内核(引擎)必须要解决的问题.

    本文从源头出发, 逐步推演, 探讨数据库内核是如何实现快照技术的. 数据库内核创建快照, 将使用如下技术:

    1. 全量拷贝(Full Clone)
    2. 写时拷贝(Copy On Write)
    3. 分区拷贝(Partitioning)
    4. 多版本(Multi Versioning, Leveling, Zero Copy)

    无论何种实现快照的技术, 数据 拷贝都不可避免, 差异点主要是不同的技术拷贝的数据量不同, 以及拷贝的数据含义不同(直接拷贝和间接拷贝). 直接拷贝意味着拷贝数据本身, 间接拷贝则拷贝数据的"指针".

    拷贝意味着 互斥, 独占, 加锁, 因为拷贝需要保证数据的完整性. 硬盘数据拷贝速度是较慢的, 往往被认为不可接受, 应极力避免. 内存数据拷贝速度较快, 但仍然需要减少拷贝的数据量.

    1: 全量拷贝(Full Clone)

    全量拷贝技术是创建快照最本能的实现方法, 也是最低效的方法. 因为数据量往往非常多, 而且还常常在硬盘上, 拷贝成本高, 耗时长. 在进行全量拷贝的过程中, 需要排斥写操作, 所以数据库系统是无法提供写服务的.

    2: 写时拷贝(Copy On Write)

    全量拷贝低效速度慢, 那么, 一个可能的优化方向是在某些不需要拷贝的场景不做任何拷贝. 在某些场景, 例如读请求, 虽然需要创建一个快照并且去读快照, 但是, 读操作并不会修改快照数据和原来的数据, 如果这时整个系统也正好没有任何写操作请求, 那么, 就没有必要做全量拷贝.

    同时, 为了应对某个时刻收到写操作请求, 需要随时做准备, 一旦有写操作请求时, 再做一次全量拷贝. 写时拷贝技术是一种 概率优化技术, 不像纯朴想法所认为地去优化全量拷贝本身的性能.

    概率优化是一种 外部优化, 依赖于实际使用场景的概率分布, 在遇到 bad cases 时, 因为内部依然非常慢, 所以在 bad cases 场景不起作用. 所以, 仅仅写时拷贝技术并不能从根本上解决问题.

    写时拷贝的关键是引入一个 单点标记和额外的一步 必要操作路径(单点), 所有的请求都必须走这条路径, 这样才能截获写操作请求, 在写操作请求之前进行数据拷贝.

    3: 分区拷贝(Partitioning)

    分区(Partitioning)技术是计算机领域非常重要的技术思想, 有点像"分而治之"思想, 和"并发"技术是统一的. 因为无论针对快照或是原始数据的修改, 在操作过程期间往往只修改很小的一个比例, 例如一个拥有一百万行记录的数据库表, 在创建完快照和销毁快照这段时间内, 也许只修改了两三行记录, 没有必要拷贝整个表.

    分区拷贝将数据拆分为多个部分(Partition), 结合 Copy On Write 技术, 在有必要的时候, 才拷贝被修改的那部分数据. Partitioning 技术在计算机领域应用非常广泛, 像我们常说的"减小锁粒度", "分布式数据库 Sharding", "并发"等等, 这些都是 Partitioning.

    4: 多版本(Multi Versioning)

    多版本技术抛弃了纯朴观念里的"修改"一词, 当想要修改某项数据时, 只是简单地写新数据写到其它地方, 暂时不管旧数据是怎么样的. 然后, 引入一个成本极小的标记, 修改数据的指向(也即将旧数据标记为作废, 将新数据标记为有效). 这个标记和前文介绍写时拷贝技术时提到的"必要路径"是一个意思.

    基于多版本技术, 创建快照时不再拷贝任何原始数据(Zero Copy), 只有成本极小的对标记的修改操作, 所以, 无论数据是在内存还是在极慢的硬盘里, 都不影响创建快照的速度. 如果这个标记是放在内存中的, 那么, 针对1TB的数据库每秒创建百万个快照也没有问题.

    不过, 多版本技术也有缺点. 它虽然不影响创建快照的速度, 也很少影响写操作的速度, 但是, 它严重影响读操作的性能, 因为我们必须读取全部的版本出来, 才能知道哪个版本是我们需要的, 版本越多, 性能就越差. 所以, 使用多版本技术时, 都要结合 垃圾回收(GC)技术, 尽快删除不需要的版本.

    和分区技术不同, 多版本技术的不同版本是指同一个对象, 不是独立的, 可以把版本理解为层(Level), 最终多个层合并(Merge), 形成最终的对象数据. 而分区是独立的, 不需要合并, 只需要连接(Chain)起来.

    在实践中, 多版本技术往往要通过扩大数据对象的粒度来减少版本数量.

    Related posts:

    1. 并发编程的核心技术 – 多版本(Multi Version)
    2. Redis 的作者狂喷某 NoSQL 数据库
    3. 解决从github匿名获取只读repo错误
    4. Linux 核心编程 – fsync, write
    5. C# 中实现 FIFO 缓冲区–ArrayBuffer

    单次续航1003公里 丰田Mirai氢燃料电池车创世界纪录

    $
    0
    0

    6月4日消息,今日晚间,丰田中国通过官微宣布, 丰田在电动化技术方面再次取得重大突破,氢燃料电池车全新Mirai续航打破世界纪录,一次加氢可行驶1000km以上。5月26日,全新Mirai从法国巴黎南部奥利省HYSETCO出发,进行了一次历时22小时的续航挑战,一次加氢行驶距离达1003km。

    值得一提的是,为了庆祝此项创举,还首度使用Mirai氢燃料发电机点亮了埃菲尔铁塔。

    这项纪录,超过此前现代氢燃料车Nexo的778公里纪录。

    据媒体报道,在测试结束后,Mirai的仪表盘上还显示有9公里续航。

    根据测算, Mirai的平均燃料消耗量为0.55公斤氢气/100 公里,按国内氢能每公斤30元计算,Mirai的行驶成本在16.5元/100公里。

    据悉,丰田全新Mirai搭载的电动机综合最大功率可达13 4KW,峰值扭矩可达300N·m,车辆的最高时速为175km/h。马力与一辆1.8T发动机的动力相当。

    新车配备三个储氢罐,可储存大量氢气,从而为电动机持续提供电能。

    与初代Mirai相比,全新Mirai全方位提升,包括车型底层架构平台全面革新,

    此外,内饰中控台造型,与雷克萨斯呈现出异曲同工之妙,全液晶仪表盘和悬浮式中控大屏,科技感充足,车内大面积使用皮质包裹。

    前不久, 丰田汽车社长丰田章男连发多条微博为氢能源站台,他表示,丰田面向未来努力实现碳中和。

    查看评论


    京东到家MySQL容器化实践

    $
    0
    0

    【编者的话】本文介绍了京东到家MySQL容器化的实践,包括基于容器的底层资源平台、监控系统及数据库自动化运维平台。同时也详细介绍了具体的技术实现,包括软硬件选型、容器调度算法、数据库高可用实现、监控系统及数据库自动化运维平台开发。

    背景

    随着京东到家业务的快速发展,MySQL数据库的访问量越来越大,在云主机上搭建MySQL越来越无法满足我们的要求。
    • 云主机的云硬盘IO性能并不能满足MySQL所需的高并发访问需求。
    • 云主机所在宿主机使用不透明,当发生网络或者硬件故障时无法及时定位问题。
    • 搭建在云主机上的MySQL在有变更规格的需求时,需要关机才能变更配置。
    • 云主机上搭建MySQL成本高,采购云MySQL成本更高。


    基于上述原因,我们最终选择采购物理机,在物理机上将MySQL容器化部署来解决以上问题。

    技术方案

    我们认为一套完善的数据库运维方案包括以下几个部分:数据库底层资源平台、监控系统及数据库自动化运维平台。
    1.png

    图1 整体架构

    数据库底层资源平台

    • 在物理机上搭建Docker环境,将MySQL实例部署在Docker内,基于Docker的特性,实现资源隔离和资源超卖。
    • 自定义规则算法来进行容器的调度。
    • 在高可用方面,针对MHA和Zabbix进行二次开发,实现宕机后快速切换。


    监控系统

    监控系统采用Zabbix,其中每个容器的监控数据(CPU、内存等)通过Docker Api来获取。

    数据库自动化运维平台

    基于Python和Flask框架,开发MySQL自动化运维平台,实现MySQL完整生命周期自动化运维,提高运维人员的工作效率。

    技术实现

    数据库底层资源平台的搭建

    软硬件选型

    MySQL运行的场景需求为高并发高IO,我们在软硬件上做了如下选择:
    • 物理机:64核、256G内存、16*960G SSD组成RAID10 or 4T NVME RAID0
    • 操作系统:CentOS 7.5
    • 容器:Docker版本1.13.1 、网络模式选择host模式。(从这也可以看出,我们主要是对CPU和内存进行了资源隔离,网络层面没进行资源隔离)
    • 镜像:自定义的MySQL5.6.36镜像、MySQL5.7.22镜像


    对基于容器的MySQL实例和基于云主机的MySQL实例,同规格下进行了压测对比,云主机中MySQL QPS最大值23K。
    2.png

    图2 云主机压测结果

    容器中的MySQL充分利用了本地SSD硬盘的高IO, QPS最大值达90K。
    3.png

    图3 容器压测结果

    容器化后MySQL性能远超云主机上的MySQL,完全可以满足京东到家当前的MySQL性能需求。

    容器调度算法

    MySQL作为一种有状态的服务,DBA不需要对之进行频繁的操作,要保持相对稳定和健壮,我们自行定义了规则算法来进行容器的调度。
    • 同一集群的实例分布在不同可用区。
    • 同一集群的实例分布在不同宿主机。
    • 根据业务级别不同MySQL分布不同宿主机,核心业务在一台宿主机上不能部署过多。
    • 分库系统,各分片MySQL分布不同宿主机。
    • 优先分配CPU、内存、磁盘空间资源最空闲的主机。
    • 通过Docker超卖CPU,超卖的倍数不超过实际CPU核数2倍。


    基于以上原则,我们开发了容器调度系统,对容器的分配进行整体调度。
    4.png

    图4 容器调度系统

    MySQL的高可用实现

    到家应用服务器访问MySQL是通过域名方式来进行连接的,我们对MHA和Zabbix监控系统进行二次开发,故障时通过快速更改域名解析来进行故障切换。整个切换过程在10秒内可以完成。
    5.png

    图5 数据库高可用架构

    Zabbix监控系统发现MySQL发生宕机后,首先判断是主库还是从库,如果主库宕机由MHA Manager来进行处理,从库宕机由Zabbix监控调用脚本来进行处理。

    主库宕机:MHA Manager将Master_Log_File和Read_Master_Log_Pos最高的从库提升为新主库,同时MHA Manager也会调用DNS解析接口将主库域名解析到新主库IP。由于域名DNS解析可能存在缓存,域名更新生效时间可能比较长。故障切换系统同时会根据宕机的MySQL数据库域名查找连接的所有应用服务器IP,通过Saltstack批量修改/etc/hosts文件绑定域名到新IP,下发到各应用主机上,缩短域名解析生效时间,能达到秒级解析生效。

    从库宕机:由Zabbix调用DNS解析接口,将宕机从库绑定的域名解析到主库上,后续操作与主库宕机操作流程类似。
    6.png

    图6 数据库宕机处理流程

    监控系统

    到家的监控系统采用的是Zabbix,使用自定义模板对MySQL运行状态进行监控。需要注意的是Docker内部CPU和内存的监控数据从OS层的获取值并不准确,我们通过调用Docker API的方式进行采集,再汇总到Zabbix。
    7.png

    图7 监控数据Zabbix展示

    Zabbix通过设置触发器,当出现告警时自动执行自定义脚本,利用这个功能,可以实现MySQL故障后自愈的功能。
    • MySQL实例触发所在磁盘空间不足报警时,自动执行脚本删除占冗余的文件从而释放空间。
    • MySQL实例触发CPU使用率过高报警时,自动执行脚本将当前运行的SQL及所有连接发邮件给DBA及相关研发,以便快速找到CPU报警的原因。
    • 前面提到的MySQL宕机后自动进行域名切换,也是利用Zabbix的这个功能。


    开发自动化运维平台

    我们基于Python和Flask开发了MySQL自动化运维系统,从MySQL资源申请、实例创建、销毁、主从架构部署、集群不同角色备份策略的选择、监控添加、销毁等,将MySQL整个生命周期实现流程化和自动化。

    MySQL申请及交付
    8.png

    图8 申请信息

    研发通过数据库运维平台申请MySQL资源,经DBA审批后,程序根据容器调度算法,后端会自动创建一套主从架构的MySQL数据库集群。并为MySQL自动添加相应的账号及授权,账号类型包含:监控、备份、主从、工具类等。同时容器创建之后就包含:MySQL服务端、Zabbix客户端、SaltStack客户端、Percona Toolkit、备份脚本、慢日志切割脚本等。

    整个过程已实现自动化,并且基于镜像快速创建容器,5分钟内可交付一套完整可用的MySQL集群。

    配置变更

    得益于容器的特性,调用Docker的update命令可实时改变对应容器的CPU和内存配额。从而可以不停机进行MySQL实例规格变更,实现快速扩容或缩容。
    9.png

    图9 实例扩容

    MySQL工具

    MySQL交付之后,我们提供了大量的MySQL工具:语法分析工具、当前慢日志分析工具、MySQL连接数查询工具、从库延迟查询工具、主从关系查询工具、正在运行SQL查询工具、MySQL快速健康检查、物理机监控、错误日志等。这些工具方便了研发的平时使用,可以借助这些工具进行排查解决。
    10.png

    图10 数据库工具

    总结

    目前到家的MySQL实例有95%以上都运行在了容器中,容器化后的MySQL平台经受住了415周年庆、618、1020等所有大促考验,对于我们来说,目前的容器化MySQL平台给我们带来了三大好处:
    • 满足性能:运行在物理机Docker内的MySQL实例性能高,能满足到家多个业务场景的性能需求。
    • 降低成本:Docker容器之间可进行资源隔离,可以在同一台机器上部署多个MySQL实例。而且通过Docker超卖CPU资源,可提高资源利用率,相比在云主机上搭建MySQL成本降低了50%,比采购云MySQL成本降低了100%。
    • 提高效率:所有MySQL流程自动化,5分钟内可以交付一套可用的MySQL主从集群,运维效率得到很大提高。


    原文链接: https://mp.weixin.qq.com/s/0R9vQVFNmY8r2a00cYSv3g

      马斯克震撼演讲:世界上最可怕的事情是孩子没有内驱力!

      $
      0
      0

      马斯克指出自己成功的关键是“内驱力”,而世界上最可怕的事情是孩子没有“内驱力”。



      作为一个屡次创造历史的男人,马斯克在诸多领域都展现了他杰出的能力。他管理了6家公司,这些企业在各自从事的领域内,都有颠覆行业的潜力,包括:SpaceX(火箭发射与回收),特斯拉(电动汽车),The Boring Company(超级高铁),OpenAI(人工智能),Future of Life Institute(生命未来研究),和Neuralink(大脑芯片)。



      以下是马斯克的震撼演讲,每一位家长的必修课。



      小时候,人们常会问我,长大要做什么,我其实也不知道。后来我想,搞发明应该会很酷吧,因为科幻小说家亚瑟.克拉克(《2001太空漫游》作者)曾说过,“任何足够先进的科技,都与魔法无异。”



      想想看,三百年前的人类,如果看到今天我们可以飞行、可以远距沟通、可以使用网路、可以马上找到世界各地的资讯,他们一定会说,这是魔法。要是我能够发明出很先进的科技,不就像是在变魔法吗?



      我一直有种存在的危机感,很想找出生命的意义何在、万物存在的目的是什么。最后得出的结论是,如果我们有办法让全世界的知识愈来愈进步,那么,我们将更有能力提出更好的问题,提高全人类的智慧,为更高层次的集体文明而努力一生,这就是活着的意义。





      1





      我想做影响人类未来的事



      所以,我决定攻读物理和商业。因为要达成这样远大的目标,就必须了解宇宙如何运行、经济如何运作,而且还要找到最厉害的人才团队,一起发明东西。



      1995年,我来到加州(进入斯坦福大学念博士),想要找出提高电动车能量密度的方法,例如,有没有更好的电容器可以当作电池的替代。但那时,互联网兴起,我面临了两个选择:继续研究成功机率不大的电容器技术,或者投身网络事业。最后,我选择辍学,参与网络创业,其中一家就是PayPal。



      创立PayPal最重要的领悟,来自于它的诞生过程。我们原先打算用PayPal来提供整合性的金融服务,这是个很大、很复杂的系统。结果,每次在跟别人介绍这套系统时,大家都没什么兴趣。等到我们再介绍,系统里面有个电子邮件付款的小功能,所有人都变得好有兴趣。



      于是,我们决定把重点放在电子邮件付款,PayPal果然一炮而红。但是,当初要不是注意到了别人的反应,做出改变,我们或许不会这么成功。所以,搜集回馈很重要,要用它来修正你先前的假设。



      2002年10月,EBay用15亿美元股票收购了PayPal。马斯克是最大股东,持有11.7%,套现1亿8000万美元。



      PayPal成功后,我开始想,眼前有哪些问题,最可能影响人类的未来?我认为,地球面临的最大问题是可持续能源,也就是如何用可持续的方式,生产和消费能源。如果不能在二十一世纪解决这个问题,我们将灾难临头。而另一个可能影响人类生存的大问题,是如何移居到其他星球。





      2





      有梦想就要放手去做



      第一个问题,促使我成立了特斯拉和SolarCity(美国最大的屋顶太阳能系统供应商);第二个问题,则让我创立了太空科技公司SpaceX。



      2002年,为了解决太空运输问题,我成立了SpaceX。当时跟我谈过的人,都劝我不要做,有个朋友还特别去找了火箭爆炸的影片给我看(笑声)。他其实也没错,我从来没做过实体的产品,所以一开始真的很困难,火箭发射连续失败了三次,非常煎熬。



      但我们从每次失败中学习,终于在2008年的第四次发射成功,让猎鹰一号进入地球轨道,那时我已经用光了所有资金,幸好成功了。



      之后,我们的运输火箭从猎鹰一号做到了猎鹰九号,又开发出飞龙号太空船。最近,飞龙号在发射升空后,成功与国际太空站连接,再返回地球。我真的捏了一把冷汗,不敢相信我们做到了。



      但是,想要让人类移居其他星球,还有更多目标要达成。所以,我希望你们也来加入SpaceX或其他太空探索公司。这不是看衰地球,事实上,我对地球的未来还挺乐观的,我认为有99%的机率,人类还可以安居很长一段时间。不过,就算地球只有1%的未来风险,也足以刺激我们提早准备,做好“星球备份”。



      2003年,为了证明电动车的潜力,我创立特斯拉公司。以往很多人都认为,电动车速度太慢、跑不远、外型又丑,跟高尔夫球车没两样。为了改变人们的印象,我们开发出了特斯拉Roadster,一款速度快、跑得远、造型拉风的电动跑车。



      所以,想要开公司,你必须实实在在地做出产品原型。因为,再怎么精彩的纸上作业、PowerPoint报告,都比不上拿出实际产品有说服力。



      Roadster面世后,又有人说,“就算做得出昂贵的限量跑车,你们有本事做真正的量产汽车吗?”没问题,我们就推出四门房车Model S,证明给大家看。



      这就是我一路走来的创业历程。我想说的是,你们都是二十一世纪的魔法师,想像力是没有极限的,别让任何事情阻止你,尽情地变魔法吧。



      以下,我要分享几个追求成功的秘诀,有些你们或许已经听过,但却很值得再次强调。



      我会鼓励你们,现在是冒险的最佳时机,有梦想就放手去做,保证你们不会后悔!



      瞄准月亮,如果你失败,至少可以落到云彩上面。





      3





      我认为人们可以选择不平凡



      一个人的一生,如果没有经历几次失败,就会错过自我挑战极限的机会。



      人们太害怕失败了。人们过于放大对失败的恐惧。想象一下,失败会怎么样?可能会饥饿、会失去住所,但我觉得要有勇气去尝试。有的时候,人们自我限定了自己的能力,他们实际没有意识到自己的能力有多大。



      人生的历程中总是伴隨着无数次的成功与失败。既然我们选择了创新,就不能畏惧失败,而是从每次的失败中去咀嚼事物的本质。通过不断地试验,终能成功。



      就我而言,我永远不会放弃,我的意思是,Never!



      接受失败,但不接受放弃。



      你的目标很重要:如果我纯粹是想优化我的身家价值,我不会选择这些企业。我会在房地产或金融业,或者,坦率地说,在石油业。



      但我们需要考虑的是,人活着到底是为了什么。人活着的意义是什么。我们正在做的事情,是不是在扩张人类的智慧版图?



      我在大学时,总是想什么最能影响人类的未来。事实上,唯一有意义去做的事,就是努力提高全人类的智慧,为更高层次的集体文明而努力一生,这就是活着的意义。



      从PayPal一路走来,我一直在想:“好吧,什么是最有可能影响人类未来的因素?”而不是考虑“什么是最好的赚钱方法?”



      对我来说,我要做的是有意义的事情,尽我的所能使这个世界变得更加美好,这是我想做的事情。我想改变世界,希望能够尽我的努力,创立一个新世界,使人们享受生活,这是我想做的事情。为此,我不介意冒险。



      我希望我做的事,能对人的生活起着深远的影响。要么不做,要做就做历史性的。



      最后,令人忧虑的是今天孩子学习和进步的动力几乎全部来自外在压力和奖励。结果是他们既不会有宏伟的目标,也不会有坚韧不拔的毅力。这样的未来我都不愿意去想象。



      我相信只要有足够的内驱力,普通的孩子也可以取得非凡成就。



      我今天所有的成就源自于《2001太空漫游》作者的那句话:“任何足够先进的科技,都与魔法无异。”

      istio灰度发布流量管理_llarao的博客-CSDN博客_istio使用

      $
      0
      0

      流量治理

      istio在namespace中开启自动注入后,我们不再需要关心sidecar的生命周期与生效时间。进行流量治理的时候我们只需要与istio交互即可
      在(一)中我们已经了解了Istio是通过k8的service进行服务发现的,所以即使我们的服务没有对外服务的能力,如果需要被istio接管的话也需要配置service( istio接近所有的流量治理方式都需要基于service操作

      使用istio的注意事项

      istio对k8的service依赖很深,所以在使用时也存在一些硬性要求

      1. 服务关联:pod需要关联到服务,如果一个pod关联到多个服务,则要求服务不能同一个端口上使用不同的协议。( 在istio的0.8版本以前为了满足这一需求,istio是不允许一个pod关联到多个服务的
      2. deployment标签:istio建议deployment中显式的使用app和version标签,每个deployment都包含一个有业务意义的app标签和一个代表版本的version标签。这样istio在分布式追踪时可以通过app标签补齐上下文信息,并且可以通过app和version为遥测补齐信息

      istio的流量治理流程

      在这里插入图片描述

      控制层
      (1)管理员通过命令行或API创建流量规则
      (2)Pilot组件( 上一节中介绍过,Pilot组件类似一个master服务的功能会下发路由规则给每一个Sidecar,除了下发以外Pilot还有一个类似于解释器的功能,将用户配置的规则翻译成每个sidecar能识别的格式)将流量规则转换为Envoy可以识别的标准格式;
      (3)Polot组件将规则下发给Envoy
      数据层
      (1)Envoy拦截对应pod的IO流量
      (2)Envoy在拦截到流量时根据收到的规则将流量治理

      Istio的流量治理主要通过VirtualService( 路由规则)、DestinationRule( 目标规则)、Gateway( 服务网关)、ServiceEntry( 外部服务)来实现,其中我们最多使用的就是VS和DR


      VirtualService路由规则配置

      virtualService是istio最主要最复杂的核心配置,主要负责配置服务的调用规则,分发规则等等,在详细介绍vs功能之前,可以先查看以下的简化配置内容

      apiVersion: networking.istio.io/v1alpha3
      kind: VirtualService
      metadata:
        name: reviews
      spec:
        hosts:
          - reviews
        http:
        - match:
          - headers:
              end-user:
                exact: jason
          route:
          - destination:
              host: reviews
              subset: v2
        - route:
          - destination:
              host: reviews
              subset: v1
      • metadata_name: vs的名称
      • spec_hosts: 对应服务的service名称
      • spec_http_match: 路由规则
        上面表达式的内容解说过来为:对reviews服务的访问请求中,如果请求头的end-user的值为jason,则将流量路由到reviews服务的v2版本上,其他流量路由在v1版

      vs配置中的关键配置项

      1. hosts:表示流量发送的目标( 可以理解为拦截目标,也可以理解为配置的路由规则归属哪一个Sidecar),在k8s中hosts需要配置为服务的service域名,同namesapce下可以配置 短域名因为可以跨namespace配置路由规则,所以这里是不便于管理的,如果重新开发istio配置管理dashboard可以考虑为域名分配增加权限

      2. http: 是一个类似httpRoute的路由集合,用于处理http的流量,是istio最丰富的的流量规则

      3. tls:是一个tlsRoute类型的路由集合,用于处理非终结的tls和https流量

      4. tcp:是一个tcpRoute类型的路由集合,用于处理tcp流量,应用于所有非http和tls端口的流量

      5. exportTo:是istio在1.1版本后的新增特性,用于控制VS跨namespace的可见性。可以控制在一个命名空间下定义的vs是否可以被其他命名空间的Sidecar和Gateway使用。不填写代表全局可见,".“代表仅当前namespace可见,”* "代表所有namespace可见。


      HttpRoute

      在正常使用时,http协议的路由应用场景最多,主要介绍一下http路由的使用场景和使用方法
      在这里插入图片描述
      如上图,httproute可以通过请求中的内容为条件,对请求进行重定向、重写、重试、故障注入、流量复制等操作
      我们把这些步骤拆分一下,主要分为匹配规则,重定向,重写,重试,故障注入,流量复制这几个方面来详述。

      HTTPRoute的匹配规则

      简述:匹配规则是分发流量的重要条件,关键字段是match字段,在match字段下就是对请求路由的匹配方式。
      在这里插入图片描述

      1. uri、scheme、method、authority:这4个字段都是StringMatch类型,请求规则中都支持exact(完全匹配),prefix(前缀匹配)、regex(正则匹配)三种匹配方式

      2. headers:匹配请求中的header,是一个map类型,所以匹配时以k-v的形式比对,对每一个header的值都可以使用exact,prefix、regex三种匹配方式。
        例:

      - match: 
          - headers: 
              end-user: 
                  exact: jason
           uri:
              prefix: "/testPipeline"

      以上规则中包含了1和2的使用方式,代表匹配请求头中的end-user的值为jason且uri以/testPipeline开头的请求。

      1. port:表示请求的端口,在使用时大部分的服务都只开放了一个端口,在这种场景下可以不用指定端口(port在实际使用中使用率会小些)
      2. sourceLabels:表示请求来源服务的标签,在k8s中这个标签就是指pod的标签。
        例:
      http:
        - match:
          - sourceLabels:
              app: cicdfront
              version: v1.0.10
          route:
          - destination:
              host: cicdapi
              subset: v1.0.10
        - route:
          - destination:
              host: cicdapi
              subset: v1.0.9

      当请求服务来源为cicdfront且其pod版本为v1.0.10时会将流量转发到cicdapi的v1.0.10上,其他的请求会发送到cicdapi的v1.0.9版本上

      1.HttpRoute的路由目标

      简述:路由目标代表请求流量发送到的目标服务,关键字段是route,在match(匹配规则)下时常代表匹配规则后的发送请求的目标服务。
      当请求完成匹配规则后,我们需要对匹配到的请求进行转发,我们还是以开始的配置举例(稍微改动一下)。

      http:
        - match:
          - headers:
              end-user:
                exact: jason
          route:
          - destination:
              host: reviews
              subset: v2
           weight: 30
          - destination:
              host: reviews
              subset: v3
           weight: 70
        - route:
          - destination:
              host: reviews
              subset: v1

      通过上节的匹配规则我们可以理解match中的匹配规则为请求头中的end-user的值为jason。当匹配这一条件时route路由生效,将请求的30%发送到reviews服务的v2版本上,另外70%发送到reviews服务的v3版本上。对未能匹配的请求路由到v1版本上。
      这一转发需要搭配istio的另一个重要配置DR(DestinationRule)使用,DR种通过标签定义不同的pod服务。通过subset子集来完成请求的调用,这里按reviews服务的v1 v2两个pod理解就好。

      1. destination中的host与http中的host一致,在k8s中也是指向的service的域名,可以省略部分内容做短域名(在同namespace下的时候)。subset代表对应的dr子集。
      2. weight: 除了destination之外的另一个重要属性主要用于流量分配的比例,在一个route下多个dr下流量权重之和必须为100
      2.HTTPRedirect重定向

      简述:istio可以人工注入一些不可见的重定向规则,自定义的重定向规则在redirect关键字下。
      理解了istio的vs工作模式那么我们接下来继续看istio的重定向功能,比较常见的使用场景:网站系统的网站地址发生了变化( 并非服务器变化,只是访问地址的请求后缀变化),要求使用之前的访问地址也能够进入网站,不影响旧用户的操作。这种情况下就很适合使用istio的重定向功能

      1. uri: 替换url中的path部分
      2. authority: 替换url中的authority部分
        例:
      apiVersion: networking.istio.io/v1alpha3
      kind: VirtualService
      metadata:
        name: cicdapi
      spec:
        hosts:
          - cicdapi
        http:
          - match:
            - uri:
              prefix: /pipeline
            redirect:
              uri: /cicd/pipeline
              authority: newcicd

      以上的请求对cicdapi服务的/pipeline请求都会被重定向到newcicd服务的/cicd/pipeline上

      3.HTTPRewrite重写

      简述:istio可以在请求发送给目标服务之前对请求信息进行改写,这个改写过程是对客户端及服务端都不可见的。重写与重定向很像。
      关键字:rewrite

      1. uri: 重写url中的path部分
      2. authority: 重写url中的authority部分
        例:
      apiVersion: networking.istio.io/v1alpha3
      kind: VirtualService
      metadata:
        name: cicdapi
      spec:
        hosts:
          - cicdapi
        http:
          - match:
            - uri:
              prefix: /pipeline
            rewrite:
              uri: /cicd/pipeline
      4.HTTPRerty重试

      简述:很多时候http请求发生异常,重试都是解决异常的最直接,最简单的办法,尤其是环境比较复杂的情况下,可以提高总体的服务质量,但是这个重试逻辑如果放在客户端进行调度就过于不规范,不便于管理。istio可以支持这种重试的自动调度。
      关键字:retry

      1. attempts:必填字段,定义重试的次数
      2. perTryTimeout:每次充实的超时时间,单位可以是毫秒(ms),秒(s),分钟(m)和小时(h)
      3. retryOn: 重试的条件,可以是多个条件以逗号分隔。可用条件有以下内容
        (1)5xx:在上游服务返回5xx的返回码,或在没有响应的时候进行重试
        (2)gateway-error:类似5xx异常,只对502,503,504的路由异常进行重试
        (3) connect-failure:在连接上游服务失败时重试
        (4)retriable-4xx:在上游服务返回可充实的4xx返回码时进行重试( 这里可能不包括部分返回码,如404,405
        (5)refused-stream:在上游服务使用REFUSED_STREAM错误码重置时重试
        (6)cancelled:在gRPC应答的Header中状态码是cancelled时重试
        (7)deadline-exceeded:在gRPC应答的Header中的状态码是deadline-exceeded时重试
        (8)internal:在gRPC应答的Header中的状态码是internal时重试
        (9)resource-exhausted:在gRPC应答的Header中的状态码是resource-exhausted时重试
        (10)unavailable:在gRPC应答的Header中的状态码是unavailable时重试
        例:
      apiVersion: networking.istio.io/v1alpha3
      kind: VirtualService
      metadata:
        name: cicdapi
      spec:
        hosts:
          - cicdapi
        http:
          - route:
            - destination:
                host: cicdapi
           retries:
             attempts: 5
             perTryTimeout: 3s
             retryOn: 5xx,connect-failure
      5.Mirror流量复制

      简述:流量复制是指在流量需要发送到某个服务时,将这个请求流量复制一份到一个指定的服务上,如下图。可以将生产系统的流量复制到一个需要更新的新版本服务上,这样完全不会对生产系统产生影响,这里只复制了一份流量,数据面代理只需要住原来的流量就可以了。
      关键字:mirror
      图片

      例:

      apiVersion: networking.istio.io/v1alpha3
      kind: VirtualService
      metadata:
        name: portal-web
      spec:
        hosts:
          - portal-web
        http:
          - route:
            - destination:
                host: portal-web
                subset: v1.0.15
            mirror:
              host: portal-web
              subset: v1.0.16
      6.HttpFaultInjection故障注入

      简述:除了转发重试这些常用的http请求操作,istio还支持故障注入。主要用于测试时主动模拟一些异常场景,比如跨服务请求超时,访问中发生错误等情况。
      在istio中故障注入主要分为两种

      1. 延迟故障注入
        延迟故障注入用来模拟超时的场景,模拟网络负载等原因导致的请求失败
        配置参数如下:
        (1)fixedDelay:必选字段,表示延迟的时间,单位可以是毫秒,秒,分钟,小时。要求时间必须大于1毫秒
        (2)percentage:选填字段,配置故障发生的比例,通过这个配置可以配置故障发生的比例(无单位,支持小数,表示百分比。如果不填默认为100,注入所有请求)。
        例:
      ……
      - route:
            - destination:
                host: portal-web
                subset: v1.0.15
      - fault:
          delay:
             percentage:
               value: 1.5
             fixedDelay: 10s

      表示对portal-web服务的请求的百分之1.5会被注入10s的延迟

      1. 请求中止故障注入
        请求中止故障注入,主要是为了模拟服务端故障的情况,可以注入指定的返回码和返回信息
        配置参数如下:
        (1)httpStatus:是一个必选字段,表示中止的HTTP状态码
        (2)percentage:配置的中止故障作用在多少比例的请求上,配置方式与延迟故障的一致。
        例:
      ……
      - route:
            - destination:
                host: portal-web
                subset: v1.0.15
      - fault:
          delay:
             percentage:
               value: 1.5
             httpStatus: 500

      对portal-web服务的请求的百分之1.5会被模拟返回500的异常。


      其实当了解了vs的大部分功能之后,我们可以看到vs的作用其实与k8s的service的作用基本是一致的,只是vs是用来修饰service的服务,可以调整、组合、拼接service,将多个service拼装成一个大的,富有规则的service。


      DestinationRule目标规则

      在我们刚刚查看VirtualService时候经常能看到这样一段配置

      - route:
            - destination:
                host: portal-web
                subset: v1.0.15

      之前说这段配置指的是将流量发送给portal-web的v1.0.15的版本,这样说是为了方便理解。实际上这种说法是不严谨的,这段配置真实代表的含义是将请求转发给portal-web服务的destinationRule的v1.0.15的子集。
      DestinationRule配置样例:

      apiVersion: networking.istio.io/v1alpha3
      kind: DestinationRule
      metadata:
        name: cicdpai
      spec:
        host: cicdapi
        subsets:
        - name: v1
          labels:
            version: v1
        - name: v2
          labels:
            version: v2
        - name: v3
          labels:
            version: v3
      DestinationRule规则定义

      destinationRule经常与virtualService一起使用( dr是配合vs一起使用的,但是vs并不要求一定需要使用dr,如果不使用dr,vs会直接使用service来负载调用对应的pod),virtualService用来修饰满足什么条件后被哪个后端处理。而destinationRule描述的是这个请求到达某个后端后该怎么去处理。
      简述:vs设置服务的访问规则,具体请求服务时的规则由dr来决定。
      关键属性:
      (1)host:必选字段,表示规则的使用对象,对应的也是k8s中的service。域名解析方式与之前的host配置一致。
      (2)trafficPolicy:规则的内容定义,包括负载均衡,连接池策略,异常点检查等内容。
      (3)exportTo:Istio 1.1版本后新增的特性,用于控制DestinationRule跨命名空间的可见性,这样就能控制在一个命名空间下定义的资源对象是否可以被其他命名空间下的Sidecar执行。如果不做赋值代表全命名空间都可见。支持 “.”代表当前命名空间可见。 "*"代表全命名空间可见。
      (4)subsets:代表一个服务的子集,一般代表一个服务的版本,与vs结合使用。每个子集都是通过label来匹配对应的pod,在istio的使用要求中建议用户为每个pod分配app、version两个标签。所以subset一般以version来划分,但这并不是硬性限制。

      1.负载均衡设置

      简述:当subset中可以匹配到多个pod时可以通过istio自身的负载均衡策略将请求分配。

      1. ROUND_ROBIN:轮循算法,如果设置了dr但没有为subset设置负载策略,那么会默认使用这个策略
      2. LEAST_CONN:最少连接算法,算法实现的是从两个随机选择的后端服务中选择一个链接数最少的链接
      3. RANDOM:完全随机策略,从现有的健康可用的后端服务中随机抽取一个地址
      4. PASSTHROUGH:直接转发到客户端链接的目标地址,不做转发(这样会有k8s的service进行负载)
      2.连接池设置

      简述:通过连接池管理可以配置阈值来放置一个服务的失败影响到整个应用。istio的连接池可以配置tcp流量、http流量治理

      1. tcp连接池配置(TCPSetting):
        (1)maxConnections:表示为上有服务的所有实例的最大连接数,默认为1024.对于HTTP只适用于HTTP/1.1因为HTTP/2对每个主机都使用单个连接。
        (2)connectTimeout:TCP连接超时时间。
        (3)tcpKeeplive:是Istio1.1版本后新增的特性,定期给对端发送一个keepalive的探测包,判断连接是否可用。包含三个属性–probes(标识有多少次探测没有反应就判断连接断开,默认使用操作系统的默认配置,linux默认为9),time(标识发送探测前连接空闲了多少时间,也是用操作系统的默认配置,linux默认为2小时),interval(探测间隔,也是用操作系统的默认配置,linux默认为75秒)。
      2. http连接池配置(HTTPSetting):
        (1)http1MaxPendingRequests:最大等待HTTP请求数,默认值为1024,只适用于HTTP/1.1,因为HTTP/2协议的请求在到来时会立即复用连接,不会再连接池等待
        (2)http2MaxRequests:最大请求数,默认1024。只适用于HTTP/2服务,因为HTTP/1.1适用最大连接数配置maxConnections即可。
        (3)maxRequestsPerConnection:每个连接的最大请求数。如果不配置则不作限制
        (4)maxRetries:最大重试次数,默认为3,表示服务可以执行的最大重试次数。如果调用后端因网络抖动导致调用失败,可能会带来业务损失,一般建议配置重试,若重试成功则可以正常配置返回数据,只不过比原来响应的时间稍慢一点。但重试次数过多会对性能产生影响,尽量不要对消耗大的服务进行重试。
        (5)idleTimeout:空闲超时,定义在多长时间内没有活动请求则关闭连接。
      3.异常实例检测设置

      简述:异常点检查就是定期考察被访问的服务实例的工作情况,如果连续出现访问异常,则将服务实例标记为异常并进行隔离,在一段时间内不为其分配流量,过一段时间被移除的实例会被解除移除,尝试请求,如果访问失败会进行更长实际胺的隔离,这也是istio的熔断功能。
      参数配置:

      1. consecutiveErrors:实例被驱逐前的 连续错误次数,默认为5,对于http服务返回502,503,504的返回码会认为服务异常。
      2. interval:驱逐检查的时间间隔(驱逐检测的统计时间),默认为10秒,要求大于1毫秒,单位可以是时、分、毫秒
      3. baseEjectionTime:最小驱逐时间。一个服务被驱逐的时间等于驱逐次数乘以最小驱逐时间。所以被驱逐的实例再被再次驱逐时会变得越来越长。时间默认为30秒,要求大于1毫秒,单位可以是时、分、毫秒。
      4. maxEjectionPercent:服务的可驱逐故障实例的最大比例,默认为10%。官方不建议配置过高,过分的驱逐会影响服务的服务能力
      5. minHealthPercent:最小健康比例,是istio 1.1版本后新增的特性,当负载的实例中,如果健康的实例数量低于这个比例,istio会进入恐慌模式,异常检查功能会被禁用,所有的服务不论是否是故障实例都可以接受请求。

      例:

      apiVersion: networking.istio.io/v1alpha3
      kind: DestinationRule
      metadata:
       name: cicdapi
       namespace: paas
      spec:
       host: cicdapi
       trafficPolicy:
         connectionPool:
           tcp:
             maxConnections: 80
             connectTimeout: 25ms
           http:
             http2MaxRequest: 800
             maxRequestsPerConnection: 10
         outlierDetection:
           consecutiveErrors: 5
           interval: 4m
           baseEjectionTime: 10m
           maxEjectionPercent: 30

      检查4分钟内cicdapi服务的异常情况,连续出现5次连接异常的服务实例会被隔离10分钟,被隔离的服务数量不能超过30%。达到十分钟后实例会重新接受请求,如果依然不能正常工作会被隔离20分钟

      5.端口策略

      简述:当我们熟悉以上的连接池配置后,实际端口配置也没有什么特殊的地方,实际上就是为了某些端口配置一些特殊规则,比如最大连接数等

      例:

      apiVersion: networking.istio.io/v1alpha3
      kind: DestinationRule
      metadata:
       name: cicdapi
       namespace: paas
      spec:
       host: cicdapi
       trafficPolicy:
         connectionPool:
           tcp:
             maxConnections: 80
         portLevelSettings:
         - port: 
             number: 8081
           connectionPool:
             tcp:
               maxConnections: 100

      cicd服务的最大连接数为80,但8081端口单独配置了最大连接数为100

      6.服务子集

      简述:subset主要作用就是通过label标签配置真实的后端服务。virtualService中通过制定subset的name来引用对应的服务。
      name:必选字段,subset的名字,通过virtualService引用subset的时候就是通过name来引用的
      labels:服务标签,通过标签来引用真实的后端服务,最常用的标签为version标签
      trafficPolicy:应用到这个subset的流量策略

      例:

      apiVersion: networking.istio.io/v1alpha3
      kind: DestinationRule
      metadata:
        name: cicdapi
        namespace: paas
      spec:
        host: cicdapi
        subset: 
        - name: v2
          labels:
            version: v2
        trafficPolicy:
          connectionPool:
            tcp:
              maxConnections: 80

      dr的作用主要就是配置目标服务的匹配方式和负载均衡、熔断的服务治理方式。


      Gateway服务网关

      之前的配置不论是vs还是dr的配置都是针对服务间的访问做的样例,没有针对过外部请求访问容器服务的样例。这种请求都需要通过istio的服务网关来配置。
      样例:

      apiVersion: networking.istio.io/v1alpha3
      kind: Gateway
      metadata:
        name: cicdfront-gateway
      spec:
        selector:
          istio: ingressgateway # use istio default controller
        servers:
        - port:
            number: 80
            name: http
            protocol: HTTP
          hosts:
          - "cicdfront.com"
      apiVersion: networking.istio.io/v1alpha3
      kind: VirtualService
      metadata:
        name: cicd-front
      spec:
        hosts:
        - frontend
        - cicdfront.com
        gateways:
        - cicdfront-gateway
        - mesh
        http:
        - route:
          - destination:
              host: cicd-front
              port:
                number: 8080
      Gateway规则定义

      Gateway一般需要与virtualService结合使用,Gateway定义了服务从外面怎么访问,virtualService定义了匹配到内部服务怎么流转。
      关键配置:
      selector:必选字段,表示Gateway负载,为入口处的Envoy运行的pod标签
      server:必选字段,表示开放的服务列表

      后端服务Server

      简述:server定义了服务的访问入口

      1. port:必选字段,描述了服务在哪个端口对外开放,是对外监听端口。
      2. hosts:必选字段,为Gateway发布的服务地址,是一个域名,用来匹配virtualService的hosts,会匹配到设置了同样hosts的vs。

      例:

      servers:
        - port:
            number: 80
            name: http
            protocol: HTTP
          hosts:
          - "cicdfront.com"

      ServiceEntry外部服务配置

      简述:将网格外的服务加入网络中,像网格内的服务一样管理,实际上就是将不归属istio自动注入的服务加入到istio的服务发现。
      配置示例

      apiVersion: networking.istio.io/v1alpha3
      kind: ServiceEntry
      metadata:
        name: cicdfront-entry
      spec:
        hosts:
        - www.cicddb.com
        ports:
        - number: 80
          name: http
          protocol: HTTP
        resolution: DNS
        location: MESH_EXTERNAL
      ServiceEntry规则定义

      重要参数:

      1. hosts:必选字段,表示ServiceEntry相关的主机名,可以是一个DNS域名,可以使用前缀模糊匹配
      2. addresses:表示与服务关联的虚拟IP地址。
      3. port:表示与外部服务关联的端口。
      4. location:设置服务时在网格内还是网格外,支持以下两种模式
        (1)MESH_EXTERNAL:表示在网格外部,通过api访问的外部服务。
        (2)MESH_INTERNAL:表示在网格内部,一些不能直接注册到服务网格注册中心的服务。
      5. resolution: 表示服务的发现模式,将服务名解析到一个后端的IP上。可以设置NONE、STATIC、DNS三种模式
        (1)NONE:用于连接的目标地址已经是一个明确的IP场景是使用。
        (2)STATIC:用在已经使用endpoint设置了服务示例的地址场景中, 也不需要解析。
        (3)DNS:表示用查询环境中的DNS进行解析,前提是没有在hosts中使用通配符。
      6. subjectAltNames: 表示这个服务负载的SAN列表
      7. endpoints:表示与网格服务关联的网络地址,可以是一个IP,可以是一个主机名。endpoints通过很多字段一同生成
        (1)address: 必选字段,表示网络后端的服务地址
        (2)ports:端口列表
        (3)labels:后端的标签
        (4)locality:后端的locality,用于亲和性路由
        (5)weight:表示负载均衡的权重。

      END

      • 我们在正常使用中,多使用的一般只有virtualService,destinationRule、Gateway这三种功能。vs负责配置访问规则、dr负责配置目标实例和负载规则,Gateway负责配置入口规则。配置好这三种配置就可以满足我们大部分的使用场景。

      但在使用中会发现这几种问题

      1. istio基本所有的功能配置时都要求一个必填字段 hosts,在k8s集群中 ,这个hosts是我们服务的长短域名,也就是service的名称。所以在很大程度来讲,istio都是基于service在工作。但是istio没有图形界面提供功能配置,不论是vs,dr,gw都是需要通过yaml配置的。这种配置就不能限制用户的一些误操作,比如对一个服务配置误配置了两个vs,那么哪个会是有效的vs?对一个服务配置了两个dr,那么哪个会是有效的dr?前面提过istio在1.1版本之后新增了exportTo属性,代表当前配置能否支持跨namespace,默认都是允许跨namespace生效的,这样会不会出现误引用的情况?

      所以在使用时还是建议封装istio的yaml拼装功能,通过页面进行控制,防止误配置yaml。

      1. istio会进行服务间的流量治理,实现方式是通过iptables进行流量拦截,这样使用时为了让入口服务也能被istio治理就必须使用istio的入口网关Gateway。这样平台使用时就不能直接为用户提供route的方式访问平台。需要使用gateway进行包装才行。但并不是所有服务都需要进行包装,只有需要在集群外直接访问的服务需要进行这个包装。( 大部分都是前台服务才需要进行这个步骤:浏览器 --> cicd-front --> cicd-api,这种情况下就需要为cicdfront生成入口网关,用户需要通过入口网关访问cicdfront才能治理这部分流量

      2. 集群内不能直接访问集群外的url

      浅谈pprof

      $
      0
      0

      对于大多数 Gopher 而言,一般平时最主要的工作内容除了实现各种无聊的业务逻辑之外,剩下的就是解决各种琐碎的问题。比如:查询性能瓶颈在哪里?查询内存泄漏在哪里?好在 pprof 是处理此类问题的利器,共有两套标准库,分别适用于不同的场景:

      命令行工具「go test」就包含了 runtime/pprof,相关参数请参考「go help testflag」:

      shell> go test -cpuprofile cpu.out -memprofile mem.out -bench .

      不过和 runtime/pprof 相比,更常用的是 net/http/pprof,接下来我们主要通过它来解决一些常见问题,想要激活 net/http/pprof 的话很简单,只要导入对应的包并启动服务即可:

      import _ "net/http/pprof"
      
      func main() {
      	_ = http.ListenAndServe("localhost:6060", nil)
      }

      需要注意的是,千万别让外网访问到 pprof,否则可能会导致出现安全问题。有兴趣的读者可以尝试通过 google 搜索「intitle:/debug/pprof/ inurl:/debug/pprof/」看看反面例子。

      Profile

      pprof 预置了很多种不同类型的 profile,我们可以按照自己的需要选择:

      • allocs:A sampling of all past memory allocations
      • block:Stack traces that led to blocking on synchronization primitives
      • goroutine:Stack traces of all current goroutines
      • heap:A sampling of memory allocations of live objects
      • mutex:Stack traces of holders of contended mutexes
      • profile:CPU profile
      • threadcreate:Stack traces that led to the creation of new OS threads

      其中最常用的是 profile 和 heap,分别用来诊断 CPU 和内存问题。

      CPU Profiling

      演示代码模拟了 CPU 密集型任务(onCPU)和耗时的网络请求(offCPU):

      package main
      
      import (
      	"log""net/http"
      	_ "net/http/pprof""runtime""time""github.com/felixge/fgprof"
      )
      
      const cpuTime = 1000 * time.Millisecond
      
      func main() {
      	runtime.SetBlockProfileRate(1)
      	runtime.SetMutexProfileFraction(1)
      
      	go func() {
      		http.Handle("/debug/fgprof", fgprof.Handler())
      		log.Println(http.ListenAndServe(":6060", nil))
      	}()
      
      	for {
      		cpuIntensiveTask()
      		slowNetworkRequest()
      	}
      }
      
      func cpuIntensiveTask() {
      	start := time.Now()
      
      	for time.Since(start) <= cpuTime {
      		for i := 0; i < 1000; i++ {
      			_ = i
      		}
      	}
      }
      
      func slowNetworkRequest() {
      	resp, err := http.Get("http://httpbin.org/delay/1")
      
      	if err != nil {
      		log.Fatal(err)
      	}
      
      	defer resp.Body.Close()
      }
      

      通过 go tool pprof 查看 /debug/pprof/profile:

      go tool pprof -http :8080 http://localhost:6060/debug/pprof/profile

      结果发现 profile 只能检测到 onCPU(也就是 cpuIntensiveTask)部分,却不能检测到 offCPU (也就是 slowNetworkRequest)部分:

      profile

      profile

      为了检测 offCPU 部分,我们引入 fgprof,通过 go tool pprof 查看 /debug/fgprof:

      go tool pprof -http :8080 http://localhost:6060/debug/fgprof

      结果发现 fgprof 不仅能检测到 onCPU(也就是 cpuIntensiveTask)部分,还能检测到 offCPU (也就是 slowNetworkRequest)部分:

      fgprof

      fgprof

      实际应用中,最好对你的瓶颈是 onCPU 还是 offCPU 有一个大体的认识,进而选择合适的工具,如果不确定就直接用 fgprof,不过需要注意的是 fgprof 对性能的影响较大。

      Memory Profiling

      演示代码模拟了一段有内存泄漏问题的程序:

      package main
      
      import (
      	"log""net/http"
      	_ "net/http/pprof""time"
      )
      
      func main() {
      	go func() {
      		log.Println(http.ListenAndServe(":6060", nil))
      	}()
      
      	for {
      		leak()
      	}
      }
      
      func leak() {
      	s := make([]string, 10)
      
      	for i := 0; i < 10000000; i++ {
      		s = append(s, "leak")
      
      		if (i % 10000) == 0 {
      			time.Sleep(1 * time.Second)
      		}
      
      		_ = s
      	}
      }
      

      通过 go tool pprof 查看 /debug/pprof/head(这次不用 web,用命令行):

      heap

      heap

      通过 top 命令可以很直观的看出哪里可能出现了内存泄漏问题。不过这里有一个需要说明的问题是内存占用大的地方本身可能是正常的,与内存的绝对值大小相比,我们更应该关注的是不同时间点内存相对变化大小,这里可以使用参数 base 或者 diff_base:

      heap with base

      heap with base

      本文篇幅有限,无法列举更多的例子,有兴趣的读者推荐参考「 golang pprof 实战」。

      关于可靠通信的三条基础定理

      $
      0
      0

      广义的可靠通信不限于计算机网络通信, 只要两个物体具有发送和接收能力, 或者两个物体之间有发送和接收动作, 均属于通信范畴. 例如人于人的声音对话, 身体语言等等. 可靠通信至少包括三项要求:

      1. 不重复
      2. 不丢包
      3. 完整性(原子性)

      为了达到这三项要求, 对应有三条定理:

      • 定理一(去重定理): 排队(串行化)是解决去重问题的 唯一正确方法
      • 定理二(丢包定理): 确认和重传是解决丢包的问题 唯一正确方法
      • 定理三(原子定理): 单点标记或者自校验是完整性(原子性)的 唯一正确方法

      有些同学可能对排队理论有怀疑, 表示搞一个全局位图(标记集合)也能解决去重问题. 如果深究, 判断标记和修改标记本质上是独立的两步操作, 这两步操作就遇到去重问题, 也即, 对标记集合的操作本身也需要排队. 当然, CAS 是一种解决方案, 但 CAS 的实现内部本质也是排队.

      用这三条基础理论可以解释许多问题, 指导我们具体的代码开发, 也能指导我们进行系统架构设计.

      例如 TCP 为什么能实现可靠通信?因为它至少遵循了这三条定理. TCP 的序号用于排队, TCP 有超时重传机制 兜底(还有选择重传, 快速重传等多项技术进行优化), TCP 有 checksum 校验数据的完整性.

      例如设计一个在线商店系统, 库存超卖问题本质就是判断库存和下订单这两步操作, 是一个去重的问题. 我们做设计 review 的时候, 一定要紧紧抓住对应的基础定理, 挑战设计者有没有设计排队策略. 如果没有, 则根据定理可以直接否定设计方案. 当然, 具体实现时, 有很多技术可以实现排队策略, 比如引入外部的队列组件, 利用数据库的锁(悲观锁或者乐观锁, 显式锁或者隐式锁). 方法有很多, 但本质只对应一条定理.

      还是在线商店系统的问题, 订单和支付一般是两套独立的系统, 这就是涉及到了不丢包的问题. 根据定理, 我们必须有定时任务进行确认和补单的机制. 所谓的分布式事务(XA), 本质也要依赖确认和重传.

      数据库事务的原子性(Atomicity)的实现手段, 常常依赖单点标记(commit point), 对分散的多个对象进行确认. 还有一种比较取巧的方案, 但很少见, 那便是把多个对象相连组成一个环形, 从而不依赖单点标记即可自校验.

      实际问题千变万化, 但本质定理就是这三条, 按定理做, 就是对的, 不按定理做, 就是错的. 以定理作为讨论基础, 大家有的放矢, 信息简炼, 能快速聚焦.

      这也是我经常提到的, 要想保证系统的正确性, 一定要先在理论上, 确保基础的核心模型的正确性. 只要核心正确了, 整个系统的健壮性就能得到保证.

      Related posts:

      1. 代码 Review 要点
      2. 小心递归次数限制
      3. SSH ProxyCommand及其思想
      4. SSDB 数据库的图形化界面管理工具 – phpssdbadmin
      5. 让你的网站支持手机二维码登录
      Viewing all 11848 articles
      Browse latest View live


      <script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>