数据专栏

智能大数据搬运工,你想要的我们都有

科技资讯:

科技学院:

科技百科:

科技书籍:

网站大全:

软件大全:

【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
准备工作
电脑:安装好CentOS系统
软件:VMware
操作步骤
第一步
输入 grub2-mkpasswd-pbkdf2,会出现Enter password:(输入密码),Reenter password:(重新输入密码)
PBKDF2 hash of your password is grub.pbkdf2.sha512.10000.1B34404433526E5002B881AAD026E76B27713C1D26A1471C4907D5A31E86132920D23E5DC8E9DA211A24057CBE9384C158FF2892A26ECEDE790F8C6D3BB2C7BF.55D8820C747338D943E05484E457411F645FD6B7564A903D7CF6780528988AC6C998FB6B7DF67EC559039BC465970C82182D7FA5F36781DDB86FB40F47FE5269(提前复制下来,vi文本编辑器需要用到)
第二步
输入# cp /etc/grub.d/40_custom /etc/grub.d/40_custom-bak(把/etc/grub.d/40_custom备份一下,要是出错了,还可以找回原来的文件),# cp /boot/grub2/grub.cfg /boot/grub2/grub.cfg-bak(将启动菜单/boot/grub2/grub.cfg备份一下)
第三步
# vim /etc/grub.d/40_custom(添加以下内容)
set superusers=’root’
password_pbkdf2 root \ grub.pbkdf2.sha512.10000.1B34404433526E5002B881AAD026E76B27713C1D26A1471C4907D5A31E86132920D23E5DC8E9DA211A24057CBE9384C158FF2892A26ECEDE790F8C6D3BB2C7BF.55D8820C747338D943E05484E457411F645FD6B7564A903D7CF6780528988AC6C998FB6B7DF67EC559039BC465970C82182D7FA5F36781DDB86FB40F47FE5269
上面的内容,只有两行。第2行root后面的反斜杠,是个连行符号。意思说,后面的内容跟前面的,是同一行。
第四步
# grub2-mkconfig -o /boot/grub2/grub.cfg(使用/etc/grub.d目录下的文件,重新编译了启动菜单)
并显示了以下信息:
Generating grub configuration file ...
Found linux image: /boot/vmlinuz-3.10.0-862.el7.x86_64
Found initrd image: /boot/initramfs-3.10.0-862.el7.x86_64.img
Found linux image: /boot/vmlinuz-0-rescue-825a7cb0361a4b7cb98141a640ef42ad
Found initrd image: /boot/initramfs-0-rescue-825a7cb0361a4b7cb98141a640ef42ad.img
done
# cat /boot/grub2/grub.cfg
可以看到,新的启动菜单中已经添加了从40_custom中带入的新内容。
第五步
# reboot
重新启动系统。在显示GRUB菜单的时候,用鼠标在窗口中点一下,然后按下字母e,即要修改启动参数。屏幕上马上会显示“Enter username”(输入用户名)。输入root,又会提示“Enter password”(输入密码)。输入密码123456,又能看到修改启动参数的那个界面
第六步
去掉这个密码保护,删除新生成的启动菜单,把以前备份的启动菜单拷贝回来即可
# rm -f /boot/grub2/grub.cfg
# cp /boot/grub2/grub.cfg.bak /boot/grub2/grub.cfg
# reboot
重启之后,再试着修改GRUB菜单启动项,便不会再提示要密码,又可以修改root密码了
云计算
2019-10-15 19:59:04
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
LNMP环境
Linux + Nginx + MySQL/Mariadb + PHP
Nginx: 是一个WEB服务器,提供HTTP服务的。
MySQL/MariaDB: 是一个关系型数据,用来存数据的(用户名、密码、文章内容)
PHP:是一个编程语言,常用来做网站
Nginx是一个WEB服务器,所以用户首先访问到的就是Nginx(静态的请求,会处理图片、js、css,
接收php的请求,但是不处理)把php的请求转给后面的php-fpm
php-fpm 会处理php相关的请求(叫做动态的请求)
动态、静态
所谓静态,指的是Nginx可以直接处理的图片、js、css、视频、音频、flash等等
所谓动态,指的是这些请求需要和数据库打交道。比如,用户登录过程,比如查看一篇文章,或者写一篇文章
MariaDB的安装:
MariaDB是MySQL的一个分支。 MySQL --> SUN --> Oracle
维基百科: https://zh.wikipedia.org/wiki/MariaDB#cite_note-103_release-21
官网 https://mariadb.org/
下载:
cd /usr/local/src/
wget http://mirrors.neusoft.edu.cn/mariadb/mariadb-10.3.17/bintar-linux-x86_64/mariadb-10.3.17-linux-x86_64.tar.gz
tar zxf mariadb-10.3.17-linux-x86_64.tar.gz
mv mariadb-10.3.17-linux-x86_64 /usr/local/mysql
mkdir -p /data/mysql
useradd -M -s /sbin/nologin mysql
chown -R mysql:mysql /data/mysql
cd /usr/local/mysql
./scripts/mysql_install_db --datadir=/data/mysql --user=mysql
cp support-files/mysql.server /etc/init.d/mysqld
vi /etc/init.d/mysqld
定义:basedir=/usr/local/mysql
datadir=/data/mysql
vi /etc/my.cnf
定义:datadir=/data/mysql
socket=/tmp/mysql.sock
log-error=/data/mysql/mariadb.log
pid-file=/data/mysql/mariadb.pid
查看服务: ps aux |grep mysql
查看监听端口:netstat -lnp //看是否有3306


解压:
解压 .tar.gz: tar zxvf xxxx.tar.gz z 相对于针对gz压缩 gzip 1.txt ; gzip -d 1.txt.gz
解压 .tar.bz2: tar jxvf xxxx.tar.bz2 j 相对于针对bz2压缩 bzip2 1.txt ; bzip2 -d 1.txt.bz2
解压 .tar.xz tar Jxvf xxxx.tar.xz J 相对于针对xz压缩 xz 1.txt ; xz -d 1.txt.xz
压缩并打包: tar zcvf 123.tar.gz 123/
tar jcvf 123.tar.bz2 123/
如何验证一条命令是否正常?
敲完这条命令之后,马上运行 echo $? ,看其输出是否是0,如果是非0说明你这个命令有错误。
错误:
error while loading shared libraries: libaio.so.1: cannot open shared object file:
No such file or directory
解决: yum install -y libaio libaio-devel
服务管理:
CentOS6: chkconfig --list //列出系统所有的服务
CentOS7: systemctl list-unit-files
6: chkconfig --add mysqld //增加服务到列表,前提是mysqld文件需要在/etc/init.d/下,并且权限755
6:chkconfig mysqld on //让其开机启动
6:/etc/init.d/mysqld start == service mysqld start


MySQL/MariDB连接:
/usr/local/mysql/bin/mysql -uroot
直接敲mysql命令:ln -s /usr/local/mysql/bin/mysql /usr/bin/mysql
系统环境变量PATH: echo $PATH
PATH的作用:可以直接用PATH这些路径里面的文件,不用敲绝对路径了。
PATH=$PATH:/usr/local/mysql/bin
echo "export PATH=$PATH:/usr/local/mysql/bin" >> /etc/profile
退出终端重新进,或者 source /etc/profile
设定密码:mysqladmin -uroot password "aminglinux"
再次登录: mysql -uroot -paminglinux
mysql -uroot -paminglinux -S/tmp/mysql.sock
mysql -uroot -paminglinux -h192.168.190.128 -P3306
Alias别名:
一条命令的另外一个名字,你可以理解为外号。
举例:
网卡配置文件路径很长,每次敲这个命令都要花很长时间,可以做一个别名:
alias viens33='vi /etc/sysconfig/network-scripts/ifcfg-ens33'
vi ~/.bashrc //针对当前用户的,换一个用户就不好使了
vi /etc/bashrc //针对所有用户,不仅仅是当前用户。
在这个文件最后面增加:alias viens33='vi /etc/sysconfig/network-scripts/ifcfg-ens33'
PHP的安装:
下载PHP
cd /usr/local/src
wget http://cn2.php.net/distributions/php-7.3.9.tar.bz2
解压
tar jxvf php-7.3.9.tar.bz2
编译
cd php-7.3.0
编译参数:
./configure --prefix=/usr/local/php-fpm --with-config-file-path=/usr/local/php-fpm/etc --enable-fpm --with-fpm-user=php-fpm --with-fpm-group=php-fpm --with-mysql=/usr/local/mysql --with-mysqli=/usr/local/mysql/bin/mysql_config --with-pdo-mysql=/usr/local/mysql --with-mysql-sock=/tmp/mysql.sock --with-libxml-dir --with-gd --with-jpeg-dir --with-png-dir --with-freetype-dir --with-iconv-dir --with-zlib-dir --with-mcrypt --enable-soap --enable-gd-native-ttf --enable-ftp --enable-mbstring --enable-exif --with-pear --with-curl --with-openssl
错误1:
checking for cc... no
checking for gcc... no
解决: yum install -y gcc
错误2:

error: libxml2 not found
解决:yum install -y libxml2-devel
错误3:

error: Cannot find OpenSSL's
解决:yum install -y openssl-devel
错误4:
error: cURL version 7.15.5 or later is required
解决:yum install -y libcurl-devel
错误5:
configure: error: jpeglib.h not found
解决:yum install -y libjpeg-turbo-devel
错误6:
configure: error: png.h not found
解决:yum install -y libpng-devel
错误7:
configure: error: freetype-config not found.
解决: yum install -y freetype-devel
错误8:
configure: error: wrong mysql library version or lib not found
解决:下载一个低版本的MySQL/Mariadb
wget http://mirrors.163.com/mysql/Downloads/MySQL-5.6/mysql-5.6.39-linux-glibc2.12-x86_64.tar.gz
tar jxvf mysql-5.6.39-linux-glibc2.12-x86_64.tar.gz
mv mysql-5.6.39-linux-glibc2.12-x86_64 /usr/local/mysql5.6
改编译参数
./configure --prefix=/usr/local/php-fpm --with-config-file-path=/usr/local/php-fpm/etc --enable-fpm --with-fpm-user=php-fpm --with-fpm-group=php-fpm --with-mysql=/usr/local/mysql5.6 --with-mysqli=/usr/local/mysql5.6/bin/mysql_config --with-pdo-mysql=/usr/local/mysql5.6 --with-mysql-sock=/tmp/mysql.sock --with-libxml-dir --with-gd --with-jpeg-dir --with-png-dir --with-freetype-dir --with-iconv-dir --with-zlib-dir --with-mcrypt --enable-soap --enable-gd-native-ttf --enable-ftp --enable-mbstring --enable-exif --with-pear --with-curl --with-openssl
错误9:

ERROR: [pool www] cannot get uid for user 'php-fpm'
解决:useradd php-fpm
make
make install
配置文件:
cd /usr/local/php-fpm/
cp php-fpm.conf.default php-fpm.conf
cd /usr/local/src/php-7.3.0
cp php.ini-development /usr/local/php-fpm/etc/php.ini
cd /usr/local/php-fpm/etc/php-fpm.d/
cp www.conf.default www.conf

启动脚本:
cd /usr/local/src/php-7.3.0
cp sapi/fpm/init.d.php-fpm /etc/init.d/php-fpm
chkconfig --add php-fpm
chkconfig php-fpm on
chmod 755 /etc/init.d/php-fpm
补充:
vi 里面在一般模式下,按dd可以删除行(剪切),5dd(剪切5行),光标挪到要粘贴的位置,按p。
操作错误之后,想要撤销,可以按u,反着撤销按 ctrl r;
一般模式下按gg可以把光标定位到首行,按G可以定位到末行;
按yy复制,5yy复制5行;
显示行号,在一般模式里输入:set nu;
定位到指定的行:一般模式下直接按数字G,如10G

编译安装一个软件包的步骤:
./configure ; make; make install
grep -i 忽略大小写
补充:
diff 查看两个文件的差异的,如 diff 1.txt 2.txt
Nginx的安装:
官网:http://nginx.org/
官方文档:http://nginx.org/en/docs/install.html
下载:
wget http://nginx.org/download/nginx-1.16.2.tar.gz
解压:
tar zxvf nginx-1.16.2.tar.gz
cd nginx-1.16.2
编译:
./configure --prefix=/usr/local/nginx --with-http_ssl_module
make && make install
启动:
/usr/local/nginx/sbin/nginx
Nginx的默认虚拟主机:
虚拟主机:
HTTP1.1 --> host (域名) 一个WEB服务可以有多个站点
定义虚拟主机配置文件,以域名为命名。
iptables -nvL 查看防火墙规则
CentOS7 firewalld
自带firewalld服务,开启状态。
关闭firewalld服务: systemctl stop firewalld
firewall-cmd --add-port=80/tcp --permanent
firewall-cmd --reload
Nginx配置:
nginx -t //查看配置文件是否有错误
nginx -s reload //重载配置文件
systemctl restart nginx //重启
/etc/hosts:
vi /etc/hosts //增加
192.168.222.128 www.aaa.com
默认虚拟主机:
就是Nginx的第一个虚拟主机。
泛解析
禁掉默认虚拟主机,加一行 deny all;


补充:
快捷键 Ctrl z可以暂停一个进程。 比如,vi的时候,可以先退出vi,然后释放命令行出来。
按fg 就可以回到vi窗口里。
安装worrdpress
下载: https://cn.wordpress.org/download/
wget https://cn.wordpress.org/wordpress-5.0.2-zh_CN.tar.gz
安装worrdpress
下载: https://cn.wordpress.org/download/
wget https://cn.wordpress.org/wordpress-5.0.2-zh_CN.tar.gz
tar zxvf xxx
mv wordpress/* /data/wwwroot/blog.aminglinux.cc/
访问http://blog.aminglinux.cc/
设置数据库
创建库:create database blog;
创建用户:grant all on blog.* to 'blog'@'127.0.0.1' identified by 'pbxfuej3LR4r';
切换某个库: use blog;
查询库里面有什么表: show tables;
问题处理:
在安装wordpress过程中,需要设定网站程序目录的权限,属主设定为php-fpm服务的那个用户
chown -R php-fpm /data/wwwroot/blog.aminglinux.cc
补充:
yum install -y expect //为了安装mkpasswd命令,这个命令用来生产随机密码的
如, mkpasswd -s 0 -l 12



discuz官网:http://www.discuz.net/forum.php
yum install -y git
git clone https://gitee.com/ComsenzDiscuz/DiscuzX.git
cp -r DiscuzX/upload /data/wwwroot/bbs.aminglinux.cc
定义虚拟主机配置文件:
1)cd /etc/nginx/conf.d
2)cp blog.aminglinux.cc.conf bbs.aminglinux.cc.conf
3)修改里面的目录
4)nginx -t && nginx -s reload
开始安装:
1)改权限 cd /data/wwwroot/bbs.aminglinux.cc && chown -R php-fpm config data uc_server/data uc_client/data
2)数据库相关操作:
create database bbs;
grant all on bbs.* to 'bbs' @127.0.0.1 identified by 'li60rtvvHAfh';
3)定义数据库相关的信息
4)完成安装
补充:
1) cp :cp -r 复制目录
2)vi 批量查找替换,一般模式下输入 :1,$s/要被替换的字符/替换成的字符/g
如果字符串中含有/,则需要脱义, 1,$s/home\/123/home\/abc/g
还有一种方法:1,$s#home/123#home/abc#g

域名跳转:
vi /usr/local/nginx/conf/vhost/ruichao.cc.conf
在 server_name 那一行的域名后面再加一个域名,空格作为分隔。
nginx -t
nginx -s reload
域名重定向:
从a域名跳转到b域名
vi /usr/local/nginx/conf/vhost/blog.ruichao.cc.conf //增加:
if ( $host = blog.ruichao.cc )
{
rewrite /(.*) http://www.aming.com/$1 permanent;
}
nginx -t
nginx -s reload
测试:
curl -x127.0.0.1:80 -I blog.ruichao.cc/1.txt
补充:
状态码:200(OK) 404(不存在) 304(缓存) 301(永久重定向) 302 (临时重定向)
如果是域名跳转,用301; 如果不涉及域名跳转用302
rewrite /1.txt /2.txt redirect;

用户认证的目的:
实现二次认证,针对一些重要的目录(后台地址)
配置用户认证:
vi 配置文件 //添加:

location ~ admin.php
{
auth_basic "Auth";
auth_basic_user_file /etc/nginx/user_passwd;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /data/wwwroot/bbs.aminglinux.cc$fastcgi_script_name;
include fastcgi_params;
}
补充:
nginx location优先级:
location / 优先级比 location ~ 要低,也就是说,如果一个请求(如,aming.php)同时满足两个location
location /amin.php
location ~ *.php$
会选择下面的
nginx location 文档: https://github.com/aminglinux/nginx/tree/master/location


Nginx访问日志:
就是用户访问网站的记录。
配置访问日志:
主配置文件:
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
虚拟主机配置文件:
access_log /log/to/path main;
nginx内置变量: https://github.com/aminglinux/nginx/blob/master/rewrite/variable.md


日志里面不记录静态文件:
在访问日志里,过滤掉一些图片、js、css类的请求日志。因为这样的请求日志没有多大用,而且会占用很大的磁盘空间
如何配置?
在虚拟主机配置文件里增加配置:
location ~* \.(png|jpeg|gif|js|css|bmp|flv)$
{
access_log off;
}
补充:
tail -f /data/logs/bbs.access.log //-f选型可以动态查看一个文件的内容
> 可以清空一个文件内容
~* 表示不区分大小写的匹配 后面跟正则表达式 .表示任意一个字符

为什么要做日志切割?
/data/logs/ 里面有很多访问日志。 如果日志越来越大,可能有一天会把整个磁盘写满。你可以想象一下一个日志有100G
你如何查看这个日志? cat less tail vi
系统里有一个日志切割的服务
logrotate 工具
配置文件: /etc/logrotate.conf
子配置文件:/etc/logrotate.d/*
Nginx的日志切割配置文件:
/etc/logrotate.d/nginx
内容:
/var/log/nginx/*.log /data/logs/*.log {
daily
dateext
missingok
rotate 7
compress
delaycompress
notifempty
create 640 nginx adm
sharedscripts
postrotate
if [ -f /var/run/nginx.pid ]; then
kill -USR1 `cat /var/run/nginx.pid`
fi
endscript
}
测试执行:
logrotate -vf /etc/logrotate.d/nginx

什么是静态文件的过期时间
让图片之类的静态文件,缓存在客户端的浏览器中,在没有过期之前,浏览器不需要请求该图片。
就是为了让这些图片有一个时效性。
如果服务器上图片已经做了更新,但是客户端访问到的还是旧的。
如何配置:
vi 虚拟主机配置文件,增加或更改
location ~* \.(png|jpeg|gif|js|css|bmp|flv)$
{
expires 1d; //1d表示1天 1h表示1小时等
access_log off;
}
补充:
curl -x 用来指定目标服务器的IP和端口,例:curl -x127.0.0.1:80 -I www.aminglinux.cc
bc 是一个linux系统下面的计算器,yum install -y bc


什么叫防盗链?
两个网站 A 和 B, A网站引用了B网站上的图片,这种行为就叫做盗链。 防盗链,就是要防止A引用B的图片。
配置:
location ~ \.(png|gif|jpeg|bmp|mp3|mp4|flv)$
{
valid_referers none blocked server_names *.aming.com;
if ($invalid_referer) {
return 403;
}
}
补充:
rz 上传文件,yum install lrzsz
sz filename 这样去把这个文件推送到windows上
测试防盗链: curl -I -e "http://www.aaa.com/1.txt" http://www.aming.com/1.png
curl的-e指定自定义的referer




访问控制:
限制IP访问:
1)白名单
allow 127.0.0.1;
dney all;
``
2)黑名单
```
deny 127.0.0.1;
deny 1.1.1.1;

限制某个目录
location /admin/ //在admin目录下操作
{
allow 127.0.0.1;
allow 192.168.190.128;
deny all;
}
限制某个目录下的某类文件
```
location ~ .*(upload|image)/.*\.php$
{
deny all;
}
```
限制user-agent
```
if ($http_user_agent ~ 'Spider/3.0|YoudaoBot|Tomato')
{
return 403;
}
```
限制uri
```
if ($request_uri ~ (abc|123))
{
return 404;
}
```
补充:
curl命令用法:
curl -v -A 'aaaaaspider/3.0' -e "1111" -x127.0.0.1:80 bbs.ruichao.cc -I
-A 指定user-agent -e 指定referer -x指定访问目标服务器的ip和port -I只显示 header信息,不显示具体的网页内容
-v 显示详细的通信过程
php-fpm的配置:
/usr/local/php-fpm/etc/php-fpm.conf
包含了一个目录 php-fpm.d/*.conf
www.conf 就是其中子配置文件
www.conf配置讲解
pool 名字: [www] 可以自定义,启动后,ps aux |grep php-fpm 看最右侧,就是pool的名字
listen 指定监听的IP:port或者socket地址
这个地址需要和nginx配置文件里面的那个fastcgi_pass所制定的地址一致,否则就会502
如果监听的是socket文件,那么要保证nginx服务用户(nginx)对该socket文件有读写权限,否则502
listen.mode 指定socket文件的权限
pm = dynamic 动态模式
pm.max_children = 5 最大进程数
pm.start_servers = 2 启动几个子进程
pm.min_spare_servers = 1 空闲时,最少不能少于几个子进程
pm.max_spare_servers = 3 空闲时,最多不能多于几个子进程
php_flag[display_errors] = off //关闭错误信息的显示
php_admin_value[error_log] = /var/log/fpm-php.www.log //配置错误日志
php_admin_flag[log_errors] = on
php_admin_value[error_reporting] = E_ALL
在nginx里面配置对应的文件:

配置slow 日志
slowlog = /tmp/php.slow
request_slowlog_timeout = 1
配置open_basedir
php_admin_value[open_basedir] = /data/wwwroot/bbs.ruichao.cc:/tmp
配置多个pool
定义多个配置文件,在配置文件中指定不同的listen地址 不同的 [pool_name]
vim /usr/local/php-fpm/etc/php-fpm.d/bbs.conf内容如下:
[bbs]
user = php-fpm
group = php-fpm
listen = /tmp/bbs.socket
listen.mode = 0666
pm = dynamic
pm.max_children = 10
pm.start_servers = 3
pm.min_spare_servers = 3
pm.max_spare_servers = 5
slowlog = /tmp/php.slow
request_slowlog_timeout = 1
php_flag[display_errors] = off
php_admin_value[error_log] = /var/log/fpm-php.www.log
php_admin_flag[log_errors] = on
php_admin_value[error_reporting] = E_ALL
php_admin_value[open_basedir] = /data/wwwroot/bbs.ruichao.cc:/tmp
查看php.ini路径:
1) /usr/local/php-fpm/bin/php -i |head
2)用phpinfo
补充:

curl -k -H "host:bbs.ruichao.cc" http://127.0.0.1/phpinfo.php
或curl -k -H "host:bbs.ruichao.cc" https://127.0.0.1/phpinfo.php


MariaDB的密码重置:
如果记得root的密码:
mysqladmin -uroot -paminglinux password "aming-linux"
如果不记得root密码:
1)编辑/etc/my.cnf
增加:skip-grant
重启mysql服务/etc/init.d/mysqld restart
2)登录进MariaDB,执行
use mysql 切换到mysql库
desc user 查看user表的所有字段
update user set authentication_string=password("aming-linux") where user='root';
3)退出MariaDB,删除/etc/my.cnf里面的skip-grant, 重启服务
4)用新密码登录即可
常识: mysql在5.7.36版本之后把密码字段存到了authentication_string字段里,在之前版本存在password字段里。
update user set password=password("aming-linux") where user='root';
然后重启mysql


为什么要配置慢查询日志?
目的是为了帮助我们分析MariaDB的瓶颈点。
如何配置?
1)进入MariaDB里面执行:
show variables like 'slow%';
show variables like 'datadir';
show variables like 'long%';
2)打开配置文件/etc/my.cnf,编辑,增加:
slow_query_log = ON
slow_query_log_file = /data/mysql/aming01-slow.log
long_query_time = 2
3)重启服务
4)模拟慢查询
select sleep(5);
5)查看慢查询日志:
cat /data/mysql/aming01-slow.log
扩展:
show processlist;
show full processlist;
mysql -uroot -pxxxx -e "show processlist"






































































































































云计算
2019-10-14 23:56:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
说起当前最火的技术,除了最新的区块链、AI,还有一个不得不提的概念是 Serverless。Serverless 作为一种新型的互联网架构,直接或间接推动了云计算的发展,从 AWS Lambda 到阿里云函数计算,Serverless 一路高歌,同时基于 Serverless 的轻量计算开始登录云计算的舞台,本文将从两个部分展开: 介绍 Serverless 的概念,历史及其现状与未来的思考; Serverless Container 的概念及现状。

一、聊聊 Serverless
前文讲到 Serverless 是一种新型的互联网架构,目前尚没有官方权威的定义,可以认为: Serverless 无服务器架构是基于互联网的系统,其中应用开发不使用常规的服务进程。相反,它们仅依赖于第三方服务(例如 AWS Lambda 服务),客户端逻辑和服务托管远程过程调用的组合。
AWS Lambda 作为 Serverless 最早的框架产品,在 2014 年由亚马逊推出,但最早的 Serverless 概念并不是由亚马逊提出,下面我们来简单聊聊 Serverless 的历史。

Serverless 历史



发轫之始
2012 年云基础设施服务提供商 Iron.io 的副总裁 Ken 提出 软件的未来 ,首次提出来 Serverless 概念, 以下是原文的一段摘录: Even with the rise of cloud computing, the world still revolves around servers. That won’t last, though. Cloud apps are moving into a serverless world, and that will bring big implications for the creation and distribution of software and applications.

初出茅庐
AWS Lambda 产品的发布可以认为是 Serverless 的里程碑,在此之前 Serverless 几乎是停留在概念期,直到 14 年 Lambda 发布,让“Serverless”提高到一个全新的层面,为云中运行的应用程序提供了一种全新的系统体系架构,**Serverless 开始正式走向云计算的舞台。

崭露头角
在 AWS 发布 Lambda 之后,众多 IaaS 及 Pass 厂商争相入市, Google Cloud Functions , Azure Funcions , IBM OpenWhisk , 阿里云函数计算 ,短短数年时间 Serverless 产品已遍地开花。

未来已来
随着容器技术、IoT、5G、区块链等技术的快速发展, 技术上对去中心化,轻量虚拟化,细粒度计算等技术需求愈发强烈,而 Serverless 必将借势迅速发展,未来 Serverless 将在云计算的舞台上大放异彩!

云计算发展看 Serverless
首先,抛一个总结性观点:云计算的发展从IaaS、PaaS、SaaS,到最新的 BaaS、FasS,在这个趋势中 serverless (去服务器化)越来越明显,而 Serveless 的完善带给云计算将会是一次完美进化!

众所周知,云计算经历了从 IDC -> IaaS -> PaaS -> Serverless/FaaS 的发展历程,下面对这些概念做一些基本介绍。

IaaS



IaaS(Infrastructure as a Service) 基础设施即服务,服务商提供底层/物理层基础设施资源(服务器,数据中心,环境控制,电源,服务器机房),用户需要通过 IaaS 提供的服务平台购买虚拟资源,选择操作系统、安装软件、部署程序、监控应用。
目前知名的 IaaS 平台有 AWS、Azure、Google Cloud Plantform、阿里云以及开源的 OpenStack 等。

PaaS


PaaS (Platform as a Service) 平台即服务,服务商提供基础设施底层服务,提供操作系统(Windows,Linux)、数据库服务器、Web 服务器、负载均衡器和其他中间件,相对于 IaaS 客户仅仅需要自己控制上层的应用程序部署与应用托管的环境。
目前知名的 PaaS 平台有 Amazon Elastic Beanstalk,Azure,Google App Engine,VMware Cloud Foundry 等。

SaaS
SaaS (Software as a Service) 软件即服务, 服务商提供基于软件的解决方案,如 OA、CRM、MIS、ERP、HRM、CM、Office 365、iCloud 等,客户不需考虑任何形式的专业技术知识,只需要通过服务商平台获取软件使用即可。



BaaS
BaaS (Backend as a Service) 后端即服务,服务商为客户(开发者)提供整合云后端的服务,如提供文件存储、数据存储、推送服务、身份验证服务等功能,以帮助开发者快速开发应用。

FaaS
FaaS (Function as a Service) 函数即服务,服务商提供一个平台,允许客户开发、运行和管理应用程序功能,而无需构建和维护基础架构。 按照此模型构建应用程序是实现“无服务器”体系结构的一种方式,通常在构建微服务应用程序时使用。

IaaS、PaaS、FaaS 对比
举个例子,比如小明想开一个水果店: IDC:如果盖房子,装修、上架水果这些工作都是小明自己来做; IaaS:如果小明房子是租的,装修、上架水果是自己做; PaaS: 如果小明房子是租的,可是房子已经装修好了,但是上架水果要自己来做; FaaS:如果有一个商家提供装修好的水果店,小明只负责把水果送过来,其余上架工作都由商家来做。

总结
从 IDC → IaaS,用户不用关注真实的物理资源。 从 IaaS → PaaS,用户不再关注操作系统,数据库,中间件等基础软件。
从 PaaS → BaaS/FaaS, 用户可以很少甚至不用关注 backend,app 可以简化为一个单页面程序。
可以说,Serverless 是云计算发展到一定阶段的必然产物,云计算作为普惠科技,发展到最后一定是绿色科技(最大程度利用资源,减少空闲资源浪费)、大众科技(成本低,包括学习成本及使用成本)的产品,而 Serverless 将很好的诠释这些!

Serverless/FaaS 模型
Serverless 是基于事件驱动的编程范型,其底层的计算平台一般为轻量计算,比如容器计算 Docker。
针对该模型本文不再赘述, 下面以 AWS Lambda 及阿里云函数计算为例,简单介绍该模型。

AWS Lambda


大致流程如下: UI 驱动,通过模拟鼠标点击触发事件; 当触发事件增多时,lambda 实例自动扩容; 当触发事件减少时,lambda 实例自动缩容。

阿里云函数计算


流程大致如下: UI/Event/Message Driven 触发事件; 用户 Function 会 package 为一个 docker 镜像; 事件调度系统配合 Docker 集群运行 Docker 容器来执行 Function

Serverless 价值与影响

低成本 运营成本,Serverless 将用户的服务器、数据库、中间件委托于 BaaS/FaaS,用户将不再参与基础设施及软件的维护,尤其在大规模的集群运营上成本大幅度降低; 开发成本,对比 IaaS 或者 PaaS 平台的服务器或者操作系统,Serverless 的架构中,用户操作的是服务化的组件比如存储服务,授权服务等,可以缩短开发周期,降低开发难度。

真正的按需计费
Serverless/FaaS 区别于 IaaS/PaaS 预先分配计算资源的计费方式,其计费方式通常是按请求次数及运行时间,一方面可以最大程度利用资源,另一方面真正的按需计费降低用户的资源成本。

高扩展
Serverless 架构一个显而易见的优点即“横向扩展是完全自动的、有弹性的、且由服务提供者所管理”。

“绿色”计算
据统计,商业和企业数据中心的典型服务器仅提供 5%~15% 的平均最大处理能力的输出,本质上这是对社会资源的一种浪费。而在 Serverless 架构下,提供商将提供更细力度的计算能力,最大限度满足实时需求,资源利用率将大幅度提升,可以认为相对 IaaS 与 PaaS,Serverless/FaaS 是一种 “绿色” 计算。

NoOps
运维的发展经历了人肉运维、自动化运维、DevOps、AiOps 等,而 Serverless 带来一种新的运维模式,这种模式下用户需要管理的只有 Code 可以认为 NoOps。

Serverless 应用场景 事件驱动以及响应式架构 IoT 物联网场景中低频请求 请求对及时响应需求不够 固定时间触发计算资源利用低的业务
流量突发场景 比如短时间大流量视频转码 短周期内的流量峰值
跨云与混合云场 边缘计算 其它 ...

Serverless 未来的一些思考

细粒度的计算资源
目前主流的 Serverless/FaaS 技术底层的计算环境通常是容器比如 Docker,容器技术是一种比硬件虚拟化更轻量的实现,用户可以在虚拟机上运行大量的容器,可以更大程度的利用计算资源。
而Serverless 的需求可能是更细粒度的计算资源,比如最近华为发布的 CCI 产品容器的规格已经支持千分之一核,相信千分之一核只是开始,未来 Serverless 在细粒度资源使用上将发挥无限可能。

统一的容器调度模型
从当前 Serverless/FaaS 及容器生态的发展来看,容器基本都是运行在云主机之上比如 aws 的 ec2,阿里云的 ecs。 由于云厂商实现方式及不同产品的差异性,容器的调度框架选择不尽相同,比如有的厂商其 Serverless 产品是基于 Kubernetes 管理云主机集群进行容器编排及调度(比如华为的 CCI),而有的产品如阿里云的函数计算产品是基于自研的 Agent 进行容器调度。那么为什么没有一种产品可以为不同的 Serverless 服务提供通用的容器调度能力呢?

生态圈多样化
Serverless 的发展必然会带动其周边生态的完善,比如 BaaS 及 FaaS 产品的形态将多样化输出,举个例子: Serverless 架构下用户的 Code 是没有服务端的,而这些服务将由云厂商以 BaaS 的服务形态提供,随着 Serverless 的发展,必然会催生多样化的 BaaS 服务。

产品抽象输出
Serverless 是云计算普惠科技的重磅技术!Serverless 的出现将开发者从复杂的硬件及软件环境中解脱出来,未来可以想象 Serverless 的产品将会以更加简单的方式呈现给用户。举个例子:大家熟知的乐高积木,不同的小零件按照不同的方式组装,最终得到的作品是多样化的。 而 Serverless 天生具备这种优势,可以想象如果 Function 以服务化的方式抽象,开发者开发一个 Cloud App 需要做的事情就是在无数的 Function 里面挑选自己需要的“积木”,然后通过一种可视化的工具进行"积木"组合!

二、Serverless Container
前文讲了一些 Serverless 生态的概念及现状,从当前主流的 Serverless/FaaS 框架,如 AWS Lambda、IBM OpenWhisk、Iron.io、阿里云函数计算分析来看,其底层的计算资源通常是 Docker 容器。可以认为 Serverless 构建于容器 (Docker) 之上!

什么是 Serverless Container
Serverless Container(无服务器容器),意味着用户不再需要关注容器集群和服务器,只需关注 Docker 容器或者 Docker Image 即可。
通过分析业界主流的的 Serverless Container 产品如 AWS Fargate , Azure ACI , 华为 CCI ,可以看出: Serverless Container 提供了更为简单的体验,用户不再需要理解容器编排技术如 K8s,Swarm; Serverless Container 提供了更细粒度的的能力,比如微核粒度的 CPU 资源和 MB 粒度的内存资源; Serverless Container 提供了将容器作为基础计算单元的思路。

Serverless Container VS Kubernets
Kuberntes (K8s) 是谷歌开源的容器管理系统,类似的产品还有 Docker Swarm、Apache Mesos。这些优秀的集群管理系统,尤其是 K8s 已经在生成得到了充分验证,从使用角度来说,K8s 需要用户具备容器及容器编排、集群管理等多方面的专业知识,而 Serverless Container 对用户屏蔽了容器集群管理,用户使用起来将更简单!
结论:Serverless Container 在容器产品形态上是高于 Kubernetes 的,事实上大部分的 Serverless Container 产品都基于或者兼容 Kubernetes。

Serverless Container VS Serverless/FaaS
根据上文的分析: Serverless Container 对用户提供的是一种容器计算资源,用户不需要关心容器集群,只需要定制 vCpu、mem 及 Docker Image; Serverless/FaaS 提供的是一种服务化的计算能力,用户同样不用关心计算集群,只需指定 vCpu,mem 及 Code。
结论:FaaS 及 Serverless Container 的底层计算资源都是 Docker 容器!
本文作者:杨泽强(竹涧)阿里云技术专家
原文链接
本文为云栖社区原创内容,未经允许不得转载。
云计算
2019-10-14 15:02:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
本文作者:用户_123456789

为了提升开发者的接入体验,解答开发者的技术疑问,开放平台于近期上线了全新的专题项目“ 开发者学院 ”,聚合优质的原创技术内容或产品介绍。“开发者学院”由3个版块构成,分别是「入门级教程」、「线上公开课」、「线下沙龙」,以文字或视频形式呈现。

入门级教程
入门级教程适用于任何使用百度地图开放平台服务的开发者,解决使用过程中遇到的普遍问题,以文字形式呈现。

当然也欢迎更多有经验的开发者一同分享百度地图开放平台使用心得和干货,如果你愿意可以 在下方留言处留下你的分享主题或者个人发表技术内容的SNS ,开放君会与您取得联系,若内容优质且获得开发者(作者)授权的情况下可发表在开发者学院,同时,还会有百度专属礼品奉上。

线上公开课
线上公开课以视频形式针对初、中级开发者进行在线教学,辅以实操等内容,帮助开发者方便快速接入地图开放平台产品。


线下沙龙
熟悉开放平台的开发者都知道开放平台会不定期举办线下沙龙活动,每期主题不同,会邀请百度内部技术负责人、工程师、产品负责人以及合作伙伴进行内容分享,现场互动等。后续开发者也会从这里看到更多相关线下活动的报名信息以及往期活动内容。

原文链接地址: https://developer.baidu.com/topic/show/290409
云计算
2019-10-10 18:44:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
如果在公有云(比如阿里云, AWS)上的分布式数据库, 无法迁移到 Rainbond 上; 或是其他尚未迁移到 Rainbond 的数据库, 那么你可以使用 第三方服务 将它们注册到 Rainbond 中, 从而使得集群内服务也可以访问它们。本文将演示如何把集群外的 MySQL 通过第三方服务注册到 Rainbond 集群中, 并为其定义共享环境变量,从而解决多个服务重复定义数据库连接信息变量的问题。 如果Rainbond安装在阿里云,请注意使用阿里云RDS云数据库时必须与Rainbond集群处于同一个区域。
前期准备 请确保你已经安装了 Rainbond V5.1 或更高的版本。 需要添加的服务, 本文使用的是 Rainbond 集群外的一个 MySQL。 phpMyAdmin, 可以在应用云市中安装, 也可以通过 镜像 的方式创建.
你可以假设这个 MySQL 是非常复杂的, 比如它是一个分布式, 主从复制, 读写分享的 MySQL, 迁移的难度比较在; 那么你可以先不迁移这个 MySQL, 通过第三方服务将这个 MySQL 的实例添加到 Rainbond 集群中, 让它也可以使用 Rainbond 服务通信治理, 服务拓扑关系等功能.
步骤 1: 填写第三方服务信息
登录 Rainbond 控制台, 进入 创建应用 -> 添加第三方服务 .
填写 服务名称 , 应用名称 , 服务注册方式(以 API 注册为例) , 服务地址 等信息.
点击 创建服务 , 并在检测通过后, 点击 创建 .
步骤 2: 添加实例地址
1. 获取添加实例的 API 地址和秘钥
添加实例的 API 地址和秘钥等信息在服务的 总览 页面中, 如下图所示:
2. 能过 Restful API 添加服务实例
在你的终端中, 结合 1 中的 API 和秘钥, 输入类似以下的 curl 命令, 发起 PUT 请求, 将实例 192.168.1.107 添加到服务中. curl -X PUT \ --url http://192.168.1.200:7070/console/third_party/bb9371b3a3288e5abb329d780d85507b \ -H "Content-Type: application/json" \ -d '{"secret_key":"jErDmpot","ip":"192.168.1.107","is_online":true}' {"msg":"success","code":200,"data":{"bean":{},"list":[]},"msg_show":"修改成功"}
详细的 API 注册请参考: 基于API注册的第三方服务
步骤 3: 添加端口
创建完成后, 会进入到服务的管理页面. 在导航中选择 端口 .
点击 添加端口 , 输入端口为 3306 , 选择 mysql 协议.
添加完成后, 打开 对内服务 , 开启服务的服务通信治理功能.
这里需要注意的是, 内部的服务可以添加多个端口, 而第三方服务只能添加一个端口.
步骤 4: 定义和分享连接信息
在导航中选择 连接信息 , 然后定义 MySQL 的连接信息(连接信息实际上是服务的环境变量). 如图所示:
这样, 当其他服务依赖了这个 MySQL 后, 就可以直接使用它的连接信息. 也就是说, 定义后的连接信息, 是可以分享出去的.
步骤 5: 确认服务
打开 对内服务 后, 该服务就可以使用 Rainbond 的服务通信治理功能.
使准备好的 phpMyAdmin, 与依赖第三方服务建立依赖; 然后更新或启动 phpMyAdmin.
在 phpMyAdmin 的 Dashboard 中, 点击访问, 对其进行访问, 并输入 MySQL 的账号密码.
你应该会在浏览器中看到类似下面的网页:
这表明, 你已经成功地将集群外的服务(MySQL), 通过第三方服务添加到了集群中.
进入 phpMyAdmin 的实例容器, 查看环境变量, 你应该可以看到步骤 4 中定义的 MySQL 连接信息: # env | grep MYSQL MYSQL_PASS=rainbond MYSQL_HOST=127.0.0.1 MYSQL_USER=root MYSQL_PORT=3306
这表明, MySQL 的连接信息已经成功地被分享给 phpMyAdmin 了. 在这个例子中, phpMyAdmin 没有使用这些连接信息, 你实际的使用过程中, 你可以根据实际的情况对这些连接信息进行利用.
总结
至此, 相信你已经了解了如何将 Rainbond 集群外的服务注册到集群中, 并为定义和分享其环境变量. 第三方服务注册到集群后, 可以像内部服务一样, 使用通信治理, 服务拓扑关系等功能.
云计算
2019-09-24 09:59:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
本文作者:HelloDeveloper
很多人说,看了再多的文章,可是没有人手把手地教授,还是很难真正地入门AI。为了将AI知识体系以最简单的方式呈现给你,从这个星期开始,芯君邀请AI专业人士开设“周末学习课堂”——每周就AI学习中的一个重点问题进行深度分析,课程会分为理论篇和代码篇,理论与实操,一个都不能少!

读芯术,一个专注年轻人的AI学习与发展平台(ID:AI_Discovery)。

来,退出让你废寝忘食的游戏页面,取消只有胡吃海塞的周末聚会吧。未来你与同龄人的差异,也许就从每周末的这堂AI课开启了!

全文共 2825 字,预计学习时长 6 分钟

大家周末好呀,又见面了。这周我们来说说如何进行特征选择的问题。

一个典型的机器学习任务,是通过样本的特征来预测样本所对应的值。如果样本的特征少了,我们会考虑增加特征,比如Polynomial Regression就是典型的增加特征的算法。在前一周的课程中,相信大家已经体会到,模型特征越多,模型的复杂度也就越高,越容易导致过拟合。事实上,如果我们的样本数少于特征数,那么过拟合就不可避免。

而现实中的情况,往往是特征太多了,需要减少一些特征。

首先是“无关特征”(irrelevant feature)。比如,通过空气的湿度,环境的温度,风力和当地人的男女比例来预测明天会不会下雨,其中男女比例就是典型的无关特征。

其次,要减少的另一类特征叫做“多余特征”(redundant feature),比如,通过房屋的面积,卧室的面积,车库的面积,所在城市的消费水平,所在城市的税收水平等特征来预测房价,那么消费水平(或税收水平)就是多余特征。证据表明,消费水平和税收水平存在相关性,我们只需要其中一个特征就够了,因为另一个能从其中一个推演出来。(如果是线性相关,那么我们在用线性模型做回归的时候,会出现严重的多重共线性问题,将会导致过拟合。)

减少特征有非常重要的现实意义,甚至有人说,这是工业界最重要的问题。因为除了降低过拟合,特征选择还可以使模型获得更好的解释性,加快模型的训练速度,一般的,还会获得更好的性能。

问题在于,在面对未知领域的时候,很难有足够的知识去判断特征与我们的目标是不是相关,特征与特征之间是不是相关。这时候,就需要一些数学和工程上的办法来帮助我们尽可能地把恰好需要的特征选择出来。

常见的方法包括过滤法(Filter)、包裹法(Warpper),嵌入法(Embedding)。接下来,我们对每一种方法分举一例,来说明每种方法对应的特征选择如何进行的。

过滤(Filter)

过滤法只用于检验特征向量和目标(响应变量)的相关度,不需要任何的机器学习的算法,不依赖于任何模型,只是应用统计量做筛选:我们根据统计量的大小,设置合适的阈值,将低于阈值的特征剔除。

所以,从某种程度上来说,过滤法更像是一个数学问题,我们只在过滤之后的特征子集上进行建模和训练。

相关系数(Correlation coefficient)

我们都知道,随机变量的方差度量的是变量的变异程度,而两个随机变量的协方差(Covariance)度量的是它们联合变异程度。如果X、Y是分别具有期望 , 的随机变量,那么它们的协方差定义为 。可以看出,协方差就是X与其均值偏离程度和Y与其均值偏离程度的乘积平均值。

也就是说,如果X大于其均值,Y也倾向于大于其均值,那么协方差就为正,表明关联是正向的,如果X大于其均值,Y倾向于小于其均值,那么协方差就为负,表明关联是负向的。我们可以将其展开:


如果X和Y相互独立,那么 。(反之不一定成立)

图为 和 的示意图,其中方差 刻画了随机变量偏离均值的程度, 刻画了两者的线性相关程度,可以直观的看出,两条线的夹角越大,则x,y的相关性越大,那么 越大。

一个自然的想法是,能否将角度作为衡量两个随机变量的指标呢?事实上,我们在此基础上定义相关系数:


对应的正是 。相关系数不仅在几何上更为直观,而且将相关度的测量无量纲化,相关度就不再依赖于测量单位。

图为理想情况下,挑选的特征空间对应的相关系数。

包裹(Warpper)

与过滤法不同的是,包裹法采用的是特征搜索的办法。它的基本思路是,从初始特征集合中不断的选择子集合,根据学习器的性能来对子集进行评价,直到选择出最佳的子集。在搜索过程中,我们会对每个子集做建模和训练。

图为包裹法的流程图,其中Estimated Accuracy是机器学习分类问题的典型的性能指标。

基于此,包裹法很大程度上变成了一个计算机问题:在特征子集的搜索问题(subset search)。我们有多种思路,最容易想到的办法是穷举(Brute-force search),遍历所有可能的子集,但这样的方法适用于特征数较少的情形,特征一旦增多,就会遇到组合爆炸,在计算上并不可行。(N个特征,则子集会有 种可能)

另一个思路是随机化搜索,比如拉斯维加斯算法(Las Vegas algorithm),但这样的算法在特征数大的时候,计算开销仍然很大,而且有给不出任何解的风险。所以,我们常使用的是贪心算法:
前向搜索(Forward search)

在开始时,按照特征数来划分子集,每个子集只有一个特征,对每个子集进行评价。然后在最优的子集上逐步增加特征,使模型性能提升最大,直到增加特征并不能使模型性能提升为止。
后向搜索(Backward search)
在开始时,将特征集合分别减去一个特征作为子集,每个子集有N—1个特征,对每个子集进行评价。然后在最优的子集上逐步减少特征,使得模型性能提升最大,直到减少特征并不能使模型性能提升为止。
双向搜索(Bidirectional search)

将Forward search 和Backward search结合起来。
递归剔除(Recursive elimination )

反复的训练模型,并剔除每次的最优或者最差的特征,将剔除完毕的特征集进入下一轮训练,直到所有的特征被剔除,被剔除的顺序度量了特征的重要程度。

嵌入法(Embedding)

如果仔细思考前两种方法,过滤法与学习器没有关系,特征选择只是用统计量做筛选,而包裹法则固定了学习器,特征选择只是在特征空间上进行搜索。而嵌入法最大的突破在于,特征选择会在学习器的训练过程中自动完成。

如果你还记得降低过拟合的理论,那么一定记得Ridge Regression和LASSO:


它们实际上并没有改变本身的模型,只是在优化函数(均方误差)的基础上添加参数的范数,参数 的大小反映了正则项与均方误差的相对重要性, 越大,正则项的权重就会越大,优化参数的结果就会偏好更小的参数值。

事实上,它们是通过降低权重系数的办法来降低过拟合,那么在线性模型中,降低权重系数就意味着与之相关的特征并不重要,实际上就是对特征做了一定的筛选。

其中LASSO的方法可以将某些权重系数降为零,这意味着,只有权重系数不为零的特征才会出现在模型中。换言之,基于正则化的方法就是一种标准的嵌入法。

图为LASSO优化结果示意,均方误差项加了正则项的效果就是:整体的优化结果不会迭代到黑点(终点),而是迭代到它们的交点,而且交点往往在轴上。

除此之外,决策树也是典型的嵌入法。因为决策树是利用一个特征进行分类,我们在生成决策树的过程就是挑选特征的过程,并且根据特征的不同取值构建子节点,直到特征没有分类能力或者很小,就停止生成节点。

读芯君开扒

课堂TIPS
本文所说的相关系数又叫做Pearson相关系数,实际上,Pearson相关系数能被看作夹角余弦值的重要前提是数据的中心化,而中心化的思想来源于高斯分布。所以Pearson相关系数更多的是测量服从正态分布的随机变量的相关性,如果这个假设并不存在,那么可以使用spearman相关系数。
过滤法应用于回归问题,还可以采用互信息法(Mutual Information ),应用分类问题则可以使用卡方检验(Chi-Squared Test )。
我们将多个决策树集成起来,会获得随机森林(Random Forests ),与决策树一样,它可以在决定类别时,评估特征的重要性,从而实现特征选择的作用.xgboost也会起到类似的作用。
深度学习具有自动学习特征的能力,而特征选择是机器学习非常重要的一环,这也是深度学习能取得重大成功的原因之一。
原文链接地址: https://developer.baidu.com/topic/show/290370
云计算
2019-09-23 22:51:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
来源:Redislabs
作者:Pieter Cailliau、LucaAntiga
翻译:Kevin (公众号:中间件小哥)
简介
今天我们发布了一个 RedisAI 的预览版本,预集成了[tensor]werk组件。RedisAI 是一个可以服务 tensors 任务和执行深度学习任务的 Redis 模块。在这篇博客中,我们将介绍这个新模块的功能,并解释我们为什么会认为它能颠覆机器学习(ML)、深度学习(DL)的解决方案。
RedisAI 的产生有两大原因:首先,把数据迁移到执行 AI 模型的主机上成本很高,并且对实时性的体验很大的影响;其次,Serving 模型一直以来都是 AI 领域中 DevOps 的挑战。我们构建 RedisAI 的目的,是让用户可以在不搬迁Redis 多节点数据的情况下,也能很好地服务、更新并集成自己的模型。

数据位置很重要
为了证明运行机器学习、深度学习模型中数据位置的重要性,我们举一个聊天机器人的例子。聊天机器人通常使用递归神经网络模型(RNN),来解决一对一(seq2seq)用户问答场景。更高级的模型使用两个输入向量、两个输出向量,并以数字中间状态向量的方式来保存对话的上下文。模型使用用户最后的消息作为输入,中间状态代表对话的历史,而它的输出是对用户消息和新中间状态的响应。
为了支持用户自定义的交互,这个中间状态必须要保存在数据库中,所以 Redis +RedisAI是一个非常好的选择,这里将传统方案和 RedisAI 方案做一个对比。

1 、传统方案
使用 Flask 应用或其它方案,集成 Spark 来构建一个聊天机器人。当收到用户对话消息时,服务端需要从 Redis 中获取到中间的状态。因为在 Redis 中没有原生的数据类型可用于 tensor,因此需要先进行反序列化,并且在运行递归神经网络模型(RNN)之后,保证实时的中间状态可以再序列化后保存到 Redis 中。

考虑到 RNN 的时间复杂度,数据序列化/反序列化上 CPU 的开销和巨大的网络开销,我们需要一个更优的解决方案来保证用户体验。

2 、RedisAI 方案
在 RedisAI 中,我们提供了一种叫 Tensor 的数据类型,只需使用一系列简单的命令,即可在主流的客户端中对 Tensor向量进行操作。同时,我们还为模型的运行时特性提供了另外两种数据类型:Models 和 Scripts。

Models 命令与运行的设备(CPU 或 GPU)和后端自定义的参数有关。RedisAI 内置了主流的机器学习框架,如 TensorFlow、Pytorch 等,并很快能够支持 ONNX Runtime 框架,同时增加了对传统机器学习模型的支持。然而,很棒的是,执行 Model 的命令对其后端是不感知的:
AI.MODELRUN model_key INPUTS input_key1 … OUTPUTS output_key1 ..
这允许用户将后端选择(通常由数据专家来决定)和应用服务解耦合开来,置换模型只需要设置一个新的键值即可,非常简单。RedisAI 管理所有在模型处理队列中的请求,并在单独的线程中执行,这样保障了 Redis依然可以响应其它正常的请求。Scripts 命令可以在 CPU 或GPU 上执行,并允许用户使用 TorchScript 来操作Tensors 向量,TorchScript 是一个可操作 Tensors 向量的类 Python 自定义语言。这可以帮助用户在执行模型前对数据进行预处理,也可以用在对结果进行后处理的场景中,例如通过集成不同的模型来提高性能。
我们计划未来通过 DAG 命令支持批量执行命令,这会允许用户在一个原子性操作中批量执行多个 RedisAI 命令。例如在不同的设备上运行一个模型的不同实例,通过脚本对执行结果做平均预测。使用 DAG 命令,就可并行地进行计算,再执行聚合操作。如果需要全量且更深的特性列表,可以访问 redisai.io。新的架构可以简化为:
模型服务可以更简单
在生产环境中,使用 Jupyter notebooks 来编写代码并将其部署在Flask 应用并不是最优方案。用户如何确定自己的资源是最佳的呢?如果用户主机宕机之后,上述聊天机器人的中间状态会发生什么呢?用户可能会重复造轮子,实现已有的 Redis 功能来解决问题。另外,由于组合方案的复杂度往往超出预期,固执地坚持原有的解决方案也会非常有挑战性。RedisAI 通过 Redis 企业级的数据存储方案,支持深度学习所需要的 Tensors、Models 和 Scripts等数据类型,很好的实现了 Redis 和 AI 模型的深度整合。如果需要扩展模型的计算能力,只需要简单的对Redis 集群进行扩容即可,所以用户可以在生产环境中增加尽可能多的模型,从而降低基础设施成本和总体成本。最后,RedisAI 很好地适应了现有的 Redis 生态,允许用户执行脚本来预处理、后处理用户数据,可使用 RedisGear 对数据结构做正确的转换,可使用RedisGraph 来保持数据处于最新的状态。

结论和后续计划
1、短期内,我们希望使用RedisAI 在支持 3 种主流后端(Tensorflow、Pytorch 和 ONNX Runtime)的情况下,尽快稳定下来并达到稳定状态。
2、我们希望可以动态加载这些后端,用户可以自定义的加载指定的后端。例如,这将允许用户使用Tensorflow Lite 处理边缘用例。3、计划实现自动调度功能,可以实现在同一模型中实现不同队列的自动合并。4、RedisAI会统计模型的运行数据,用于衡量模型的执行情况。
5、完成上文中解释的DAG 特性。

更多优质中间件技术资讯/原创/翻译文章/资料/干货,请关注“中间件小哥”公众号!
云计算
2019-09-21 10:09:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
本文作者:用户_123456789
百度地图室内定位生态联盟正式成立
当下,随着移动互联网和智能手机的普及,室外GPS导航应用已经成为生活中的重要工具,拯救了无数“寻路困难患者”于水火之中。只要拥有一台智能手机,就可以在熟悉或陌生的城市里轻松找到通往目的地的便捷路径。但是,“路痴”这个词仍未完全退出历史舞台:由于卫星信号的遮挡和衰弱 ,GPS在室内几乎毫无用武之地。为了解决这一困境,百度地图在不断夯实室外、道路场景定位服务的同时,也率先启动了对室内定位服务的探索。
在日常生活中,人们有80%的时间都身处室内,所产生的信息有80%都与位置相关,室内场景遍布各行各业。面对出行环节真正的“最后一公里”,室内地图、室内定位、检索、导航、路线规划等服务非常重要,而所有的服务都依赖于定位的精准性。作为地图行业的领导品牌,百度地图推出国内首个高精度室内地图,目前已覆盖火车站、机场、商场、医院等4000+室内场所,百度地图综合应用WIFI、气压计、蓝牙、地磁传感器等实现室内定位技术,将准确度缩小至1-3米,达到商铺级定位。
此次百度地图室内定位生态联盟的成立,旨在邀请开发者、室内场所管理者一同携手,共建室内定位数据生态,提升定位的精准性,为用户提供最优质的室内定位服务。
为了使开发者和室内场所管理者更加方便快捷地参与到联盟中来,百度地图为其提供了简单易上手的数据更新方式:通过下载安装专门APP,登录进入平台配合可视化的操作指引,在商场内主干道进行路径规划并行走,只需15分钟就能够完成一层楼的数据更新。
百度地图也为加入的联盟成员提供了基础的地图服务,在百度地图App和承载了百度地图服务的应用上都可展示室内图概览,清晰的看到每一层楼每一个店铺、ATM机、厕所等位置。加入百度地图室内定位生态联盟后,合作伙伴可享有全新发布服务的优先体验权、室内定位数据的优先使用权;在服务成本上拥有最低折扣、配额并发优先提升;同时拥有更多品牌曝光以及联盟数据共享的机会。
面向用户,百度地图也提供了检索、导航、路线规划等服务,帮助用户更高效更便捷的完成找人、找物、找店、安全预警等问题。百度地图针对不同的用户场景,打造专业的解决方案,利用高精度的室内定位、AR等技术打造智慧停车场,帮助用户解决停车场停车、找车的难题。利用高精度室内定位、地理围栏、用户画像等技术分析用户潜在行为,帮助室内商铺完成精准化店铺营销,甚至在AR导航下也可以展示周边店铺信息及促销信息。
在2019年,百地地图携手联盟成员进一步探索室内场景下用户新的需求,开启室内定位蓝海,大力推动室内定位生态。
原文链接地址: https://developer.baidu.com/topic/show/290351
云计算
2019-09-20 15:45:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
为什么要写这篇文档?
在交付了很多企业级用户后,我们发现很多用户的环境都是离线的。我们一直在探索离线环境下实现源码构建的方案,以期让这些企业用户可以也可以体验到Rainbond源码构建功能带来的便捷。
那么,在离线环境下,实现源码构建会有哪些难点呢?其实这个问题的答案就是整套源码构建流程中有那些点对于互联网有依赖:
- 代码仓库:源码构建过程的起点是一个可用的代码仓库,离线环境下我们不可以使用 Github、Gitee 等基于互联网的代码仓库。Gitlab、Gogs 等私有代码仓库成为了最佳选择。有些用户已经拥有了自己的私有代码仓库,这种情况下,保证Rainbond管理节点所在的服务器可以正常访问到该代码仓库即可;而对于还没有搭建自己的私有代码仓库的用户而言,如何快速搭建一个Gitlab或者Gogs就是离线源码构建需要攻克的第一关。
- 构建私服:构建私服是指在源码构建过程中,获取依赖包的仓库,常见的有 Nexus、Artifactory 等。有些用户已经拥有了自己的私有构建私服用以管理自己的依赖包,这种情况下,我们提供方案让 Rainbond 可以直接对接私服;而对于还没有搭建自己的构建私服的用户而言,Rainbond自带的 rbd-repo 组件可以作为本地仓库使用。
- 应用运行时:应用运行时是指服务运行所依赖的环境,比如对于Java应用而言,运行时就是环境中安装的 Jdk。对于用户而言,离线环境如何配置好应用运行时是离线源码构建最大的挑战。
在明确了上述难点后,接下来的文章,会以Java应用构建为例,指引用户一步步攻克这些难关,最终达成离线源码构建的目标。
离线部署代码仓库
在离线环境下,推荐使用平台的应用离线导入功能,快速导入 Gitlab 应用并安装使用。
需要事先获取离线资源: Gitlab应用包 Gogs应用包
- 访问Rainbond应用管理平台,并导入离线应用包
Gogs离线导入方式和Gitlab一致。
至此,我们已经拥有了一个私有化的代码仓库。可以通过它来托管代码,并可以通过它实现 自动构建 。
离线对接/部署构建私服
Java源码基于Maven构建过程中,会根据 pom.xml 文件解析依赖关系,并前往指定的构建私服拉取依赖包。而在Rainbond中,安装了默认的源码构建包仓库 rbd-repo ,这个组件既可以作为已有私服的代理,也可以用来搭建本地私服,来应对不同用户的需求。
- 已有私服的对接
- 搭建本地仓库,并导入jar包
离线配置应用运行时
本节提供一个在应用中离线安装运行时(Jdk)的方案,这个方案会运行起一个私服仓库服务,这个私服仓库可以负责安装java运行所需要的Jdk环境。 有网环境下载离线资源镜像 docker pull rainbond/buildpack:java-v5.1.5 docker save rainbond/buildpack:java-v5.1.5 > rainbond-buildpack-java-v5.1.5.tgz 导入镜像
将保存下来的镜像压缩文件放到首个管理节点上,然后导入镜像: docker load -i rainbond-buildpack-java-v5.1.5.tgz docker tag rainbond/buildpack:java-v5.1.5 goodrain.me/buildpack:java-v5.1.5 docker push goodrain.me/buildpack:java-v5.1.5 运行私服仓库服务
编辑配置文件,将该服务运行起来。 vi /opt/rainbond/conf/base.yaml
在最下面一行添加如下段落 - name: rbd-java-buildpack endpoints: - name: BUILDPACK_ENDPOINTS protocol: port: 2017 health: name: rbd-java-buildpack model: http address: 127.0.0.1:2017/lang/ max_errors_num: 3 time_interval: 30 after: - docker type: simple pre_start: docker rm rbd-java-buildpack start: >- docker run --name rbd-java-buildpack --network host -i goodrain.me/buildpack:java-v5.1.5 stop: docker stop rbd-java-buildpack restart_policy: always
启动服务 node service update
验证服务是否运行 grctl cluster
对接rbd-repo并修改远程仓库 所有节点rbd-repo都需要调整
修改远程仓库 pkg_lang 对应URL为 http://<首个管理节点IP>:2017/lang/
至此,离线环境下的Java源码构建环境就配置完成了。
云计算
2019-09-19 10:22:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
使用IntelliJ IDEA做为开发工具,对基于maven的java工程,如果要编写lambda表达式,先确保安装并使用了jdk1.8或者更高版本,然后再要做一些设置才能正常编译和执行,具体表现在maven支持和intellij idea工具支持两个方面,配置如下:
##maven支持## 在pom.xml中增加一个插件,使得maven支持jdk1.8语法: org.apache.maven.plugins maven-compiler-plugin 3.2 1.8 1.8
修改intellij idea配置之一:修改intellij的全局设置##
接下来是intellij的全局设置,选择菜单中的全局设置,如下图红框:
在弹出的菜单中,设置Java compiler的level为1.8,具体的设置如下图红框所示:
修改intellij idea配置之二:修改项目工程配置##
然后是项目工程的属性支持lambda,在工程上点击右键,选择“Open Modular Settings“,如下图:
在弹出的菜单中的language level选择“8 - Lambdas“,如下图:
完成并使用
这些设置完毕之后,就能在代码中写lambda表达式了,并且ide还会自动提示,如下图:
欢迎关注我的公众号:程序员欣宸
云计算
2019-09-19 09:07:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
在ELK in K8s demo中,进行了基础镜像的构建,完成了基础环境的搭建。在实际的开发部署当中,可以采用GitRepo来提高效率。本文将以更改频繁的logstash pipeline为例进行阐述。 logstash pipeline的代码在企业内部,不公开。 该仓库中文件结构如下,文件含义在demo中已说明。
.
├── logstash.conf
└── patterns
└── zstack 后续对logstash的pipeline做相关修改后,将其提交到git lab。 已为logstash实例添加了gitrepo volume(见 logstash git repo volume ),此时删除logstash pod后,replicaset-for-logstash为维持replicas=1,将创建新的logstash pod并pull最新版本的git repo作为logstash的配置。
( 以下操作务必在测试环境中验证通过后再部署到生产环境 )
kubectl delete pod replicaset-for-logstash-xxxx logstash是否应该与git repo保持同步? 目前pod logstash中只有一个container用来运行logstash进程,为了使logstash的配置与git repo保持同步,可在该pod内添加一个辅助container(运行的镜像可在docker hub上通过检索关键字"gitrepo sync"获取)。两个container挂载同一volume,由sidecontainer来保持与git repo的同步。 但是不建议logstash与git repo保持同步。在某些情况下,对logstash pipeline做修改后并不想立即使用最新修改,如:旧版本pipeline仍需要继续工作一段时间、修改的pipeline需要提交到git lab但还需要添加新的功能。
云计算
2019-09-18 09:05:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
本文整理自李样兵在北京站 RocketMQ meetup分享美菜网使用 RocketMQ 过程中的一些心得和经验,偏重于实践。
嘉宾李样兵,现就职于美菜网基础服务平台组,负责 MQ ,配置中心和任务调度等基础组件开发工作。
今天主要从三个方面进行分享: 美菜网消息队列的历史 基于 RocketMQ 我们做了那些事情 同城双活的选型和思考
美菜网消息队列的历史
美菜网历史上是多套 MQ 并存,Kafka 用于大数据团队;NSQ 和 RocketMQ 用于线上业务。
多套集群存在的问题:
1、维护和资源成本高昂:Kafka 用 Scala 语言, NSQ 用 GO 语言, RocketMQ 用 Java 语言,维护成本较高,每套 MQ 不论消息量多少,至少部署一套,资源成本较高。
2、易用性较差:三套 MQ 基本上都是开箱直接使用,二次开发比较少,业务接入不方便,使用混乱。消费者接入时,需要知道 topic 在那套集群上,使用哪种客户端接入。
3、可靠性:比较了一下 RocketMQ 和 NSQ 内置的复制机制。NSQ 多通道之间是复制的,但是其本身是单副本的,存在消息丢失的风险。
统一集群的选型比较:
1、功能性,核心的功能每个 MQ 都有,考虑更多的是附加功能,比如延迟消息、顺序消息、事务消息,还有就是消息的回溯、基于 key 的检索。
2、可靠性, RocketMQ 就像前面几位老师说的,有多种刷盘和同步机制,可以结合自己的需求灵活配置,美菜网用了 2 年多时间,表现一直比较稳定。
3、技术栈的匹配,公司是以 java 语言为主,php 为辅。
4、社区完备性来说, RocketMQ 社区是比较活跃的,而且支持也是比较到位。
可以通过微信、钉钉、邮件,还有像今天这样的线下沙龙,这也是我们考虑的一个非常重要的点。
统一集群的迁移方案:
1、协议的兼容, RocketMQ TCP 协议,对 java 原生支持,仅需依赖一个 jar 就可以进行使用了, NSQ 使 http 协议。
2、业务的无感,迁移过程中,解耦生产者和消费者迁移,实现平滑的迁移。
3、消息不丢失,迁移过程中消息必然是不能丢失消息的,很容易理解。我们来看下图,这个是我们当时迁移时的解决方案。
左边是 Producer ,业务通过 Http 连接 NSQ ,对于生产者,实现一个 http 网关,来接收业务生产消息转发到 RocketMQ 。对于消费者,实现一个 transfer 的工具,将消息透传到 NSQ ,这样对消费端是无感的,生产端完成迁移了,消费者可以逐步的往 RocketMQ 上迁移了,所以整个迁移过程还是比较顺利的。
基于RocketMQ我们做了那些事情
诉求:
1、多语言支持,前面已经提到了美菜网的技术栈以 Java 语言为主,还有 php , go , python 语言等。
2、易用性,业务接入快捷,方便。
3、稳定性,保证整个平台的稳定可靠。
多语言的支持:
生产处理器,提供 HTTP 协议消息生产支持;消费处理器,消费端的网关,不断从 RocketMQ 拉取消息,通过 http 发送到消费端client;流量调度器,根据 topic 的 SLA 做路由、流量调度。
易用性:
主要是从业务使用角度,降低业务的接入成本,提高业务接入的效率。
1、自定义 SDK ,同时定义了一个 spring 标签库,使用起来简单。
2、加入了一些 trace ,指标采集功能,对消息积压和失败的报警。
3、消息轨迹,消息从生产到 broker ,再到消费有一个完整的可以追踪的功能,这样出现了问题就可以很容易的排查,防止出现生产者说发了消息,消费者说没有收到消息的相互扯皮的问题。
4、失败消息补发, RocketMQ 是有失败重试机制的,失败消息会进行 16 的失败重试,最终到死信队列中,不再投递。可能业务系统出现了故障,经过较长一段时间的解决,解决之后希望消息可以重新发送。

稳定性:
1、集群隔离,我们会按照 SLA 隔离出业务集群、日志集群、计算集群。业务集群采用的主从同步,同步落盘,计算集群采用主从异步,异步落盘,日志集群就是单主结构
2、完善故障预案
节点故障,快速下线,一键扩容。
主节点挂掉,从节点提升为主节点,主节点改为只读。
3、完善监控报警机制
生产延迟**, TPS , TP99 多维度指标数据
**
同城双活的选型和思考
背景:
1、保证数据可靠性,如果所有数据都在一个机房,一旦这个机房出了问题,数据有丢失的风险。
2、机房的扩容,单机房毕竟容量有限,多个机房可以分担流量。
方案选型:
1、同城冷备,备用一套服务存在但不对外提供服务,当另一套服务有问题时进行切换,但是真的出了问题,我们是否敢切流量呢?
2、同城双活,平时就是双机房对外提供服务,出问题的时候切掉故障机房,真正实现容灾的目的。
几点诉求:
1、机房就近,生产者在a机房,生产后的数据也在 a 机房的 broker ;消费者在b机房,消费的消息也来自 b 机房 broker 。
2、应用平滑迁移,支持按 topic ,应用逐步迁移。
3、故障的快速切换。
几个关键点:
就近识别算法:
1、 IP 段的方式,不同的 IP 段表示不同的机房,该方案对公司网络要求较高,公司网络调整,也需要修改修改算法,升级客户端。
2、协议层增加机房标识,在生产和消费的 client 通信的时候都添加上所在机房标识,改动成本较高。
3、 broker 名字增加机房标识,客户端 clientID 增加机房标识,该方案改动成本较低,对 MQ 核心功能无入侵。
数据复制:
实现主-从-从结构,基于 slave 异步复制,减轻 master 节点的压力。
故障预案:
机房或链路出现问题时。需要关闭一层机房的写权限。
机房接入层故障,无影响。
我们接下来要做的事情
1、大规模集群化的运维。目前的情况下,当大规模集群需要运维的时候是很棘手的,如果实现真正的无人值守的就会好很多。
2、按 SLA 进行自动 topic 路由调整。目前这个需要我们和业务方去提前沟通确认好,人工来调整,未来期望可以自动调整。
本文作者:李样兵, 2012 年研究生毕业, 2016 年加入美菜,现就职于美菜网基础服务平台组,负责 MQ ,配置中心和任务调度等基础组件开发工作。
本文作者:中间件小哥
原文链接
本文为云栖社区原创内容,未经允许不得转载。
云计算
2019-09-16 16:03:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
作者 | 赵钰莹 近日,Linux 基金会宣布成立 Reactive 基金会。对于 Reactive,各位开发者应该并不陌生,尤其是 Node.js 开发者,但真正了解并意识到这件事情对开发方式带来的影响的恐怕不多。本文,InfoQ 有幸第一时间对推动阿里巴巴成为该基金会初创成员的阿里巴巴资深技术专家雷卷进行了独家采访,进一步全面了解 Reactive 基金会的成立背景及其对开发方式的发展推动。
Reactive 基金会是什么?
近日,Linux 基金会 宣布启动  Reactive 基金会,旨在加速发展构建下一代网络应用程序的架构。该基金会由阿里巴巴、Facebook、Lightbend、Netifi 和 Pivotal 等初始成员组成。涉及成功的开源规范 Reactive Streams 和 RSocket,以及编程语言实现。
虽然 Reactive 基金会刚刚成立,但 Reactive,也就是开发者常说的响应式编程,已经发展多年。2011 年,Reactive 就开始步入大众视野, 当时微软在 .Net  Framework 4.0 中内置了Reactive 支持,称之为 Reactive Extensions。2013 年,广大 Java 开发者熟知的 RxJava 发布。接下来,Reactive 进入飞速发展阶段,先后出现了 RxRuby、RxJS(感兴趣的开发者可以访问  http://reactivex.io/   )等。
在技术的发展过程中,各大厂商也开始陆续跟进,比如 Reactive 宣言、Lightbend 的 Akka、Spring 的 Reactor 项目,Spring Reactive Web 等,新兴的微服务框架基本都是 Reactive 的,比如 Vert.x,Micronaut 等。 很多开发者对 Reactive 的理解是开发包,比如 Android 中整合 RxJava、Spring 中的 Reactive Web。但是,RSocket 的出现将 Reactive 扩展到分布式场景,让所有应用都可通过 Reactive 和 RSocket 的方式串联起来。
至于为什么 Reactive 可以迅速被广大开发者接受,比较典型的两大价值如下: 非阻塞和高并发: Reactive 基于异步消息通讯,与 Node.js 的 Event Loop 设计类似,这样的应用没有同步阻塞,系统吞吐率较高,相当于提升了系统性能。之前,开发者采用线程池来实现并发,现在通过 Actor 的消息模型,消除了获取线程的等待,减少了大量线程的切换,CPU 利用率提升。此外,非阻塞和高并发对云上客户尤为重要,CPU 利用率和 QPS 提升直接意味着账单金额变少; 函数式编程范式: 函数编程已经越来越被开发者接受,拥有线程安全、高效执行等优点,但是将这些函数串联起来工作,需要一定范式和相关框架,这就是 Reactive 做的事情,比如标准的 Reactive Streams 规范,相关的开发框架 RxJava、Reactor 等,可让函数编程更加简单,而且代码还是统一风格,阅读简单的同时,Code Review 也非常容易。这就好比 Java 中有 Servlet 规范,众多 Web 框架来简化 Web 应用开发一样。如果在代码中看到 filter、 map、flatMap、subscribe 等函数调用,基本就是 Reactive 的雏形。
Reactive 基金会的出现则无疑会将这一技术的价值在未来最大化。据雷卷透露,整件事情在 2018 年底就已启动。当时,阿里巴巴与 Netifi、Pivotal 和 FaceBook 在共同开发 RSocket 相关开源产品。虽然,Reactive 这个词经常被提及,但是知道的开发者还是比较少,几家公司觉得需要让更多开发者知道这项技术,因此有了成立基金会的想法。雷卷表示: 整个筹备过程花费了不到半年的时间,主要得力于 Netifi 和 Pivotal 的支持,虽然很多人还不是特别了解 Reactive,但是 Linux Foundation 的很多人对技术方向的把握还是很到位的,所以在半年不到的时间就完成了 Reactive 基金会的创建。
大部分开发人员对这几家初创成员公司都不陌生,比如微服务领域比较火的 Spring Framework 就是 Pivotal 在维护开发。之后,这几家公司将共同参与到基金会的工作中,雷卷表示,Pivotal 主要致力于将 RSocket 与 Spring 生态进行融合;Netifi 是 RSocket SDK 核心开发团队,同时有自己的 RSocket Broker 商业产品;Facebook 也致力于 RSocket 的开发,其中,RSocket-cpp 和 RSocket-js 主要都是 Facebook 的工程师在维护,同时,Facebook 内部也在积极推动 RSocket 落地;Lightbend 可能很多开发者觉得陌生,但它是 Scala 语言背后的支持公司,同时也是响应式编程的先行者,支持着 Akka 平台的开发;阿里巴巴主要集中于 RSocket 的开发,基于 Reactive 和 RSocket 将分布式开发推向下一个高度,同时也在积极开发内部的 RSocket 产品,该产品将为云上客户提供服务。
Reactive 基金会成立的出发点就是推动 Reactive Streams 和 RSocket 的发展,这对后续的 Reactive 产品开发有非常大的指导意义,保证了这些 Reactive 产品在 API 和协议上都是兼容的,这对最终开发者而言非常重要。
具体来说,Reactive 基金会将首先推动 RSocket 1.0 规范的落地,包括各种语言的 SDK 开发,这是一件工作量大且繁琐的工作,各种语言、测试、性能等相应工作都需要配合进行。对开发者来说,Reactive 基金会将带来很多价值,比如持续推动规范演进、SDK 开发、文档、会议支持等,同时也是各 Reactive 产品对开发者的介绍窗口。
Reactive 对开发方式带来的改变?
事实上,Reactive 的出现对现有架构带来许多冲击,比如基于事件驱动设计、流式处理、Service Mesh、无网络依赖、安全等。相应的,开发方式也会随之发生改变,越来越多的框架开始全面支持 Reactive,比如 Spring WebFlux、Spring Data Reactive Repository 等;数据库操作,通过 R2DDBC 方式已经完全 Reactive 化;各种 NoSQL 产品,比如 Redis、MongoDB 等,早已支持 Reactive;RPC 和 HTTP REST API 在 RSocket 的影响下可能也会改变开发者对分布式通讯的认识。在这些改变和冲击中,最典型的就是代码编写和分布式通讯。 代码编写: 我们之前过程式的代码,将会被函数式编程和 Reactive 范式替代,之前代码中大量的 if else, null 判断,for 循环,try/catch 等,都会极大地减少,取而代之是 Reactive 的各种标准操作,更加简单明了,代码量也会显著减少。 分布式通讯:之前分布式开发,我们非常注重通讯细节和并发的处理,这个是核心,Reactive 和 RSocket 的介入,会让开发门槛会降到最低,你几乎不用关心通讯的底层细节、并发线程数处理、断路保护等,这一切 Reactive 下的 RSocket 就给你解决啦,你可能只用关系接口设计和数据序列化。现在的应用越来越追求极致性能,CPU、GPU 和 FPGA 等全都上,如果没有匹配的高效通讯协议,那么性能会打折不少,而这一切 RSocket 都可以帮你做到,而且更加简单和优雅,我们稍后也会介绍 RSocket。
可以说,即便开发者现阶段不关注 Reactive,这一暗潮也已经在涌动。与此同时,业界也存在很多 Reactive 和 Java 整合的一些技术,这些技术让 Reactive 更加成熟且稳定: Reactive 框架: RxJava, Reactor, Akka, Kotlin Coroutines & Flow Web 框架: Spring WebFlux, Vert.x, Micronaut, Helidon 数据层: Spring Data Reactive,支持 database, Redis, Cassandra, MongoDB 等等 通讯层: RSocket, Reactor Netty, Reactor Aeron,Reactive Dubbo 集成:  Reactor Kafka, Reactor RabbitMQ,RocketMQ 等
如今,越来越多的开发者开始习惯基于云平台开发应用。如上文所言,Reactive 的发展对云原生领域的发展也将起到很大作用。在采访中,雷卷透露,云原生可让应用独立于底层架构,保持中立,进而可运行在不同云厂商的底层架构上。 但是,这一方式并没有解决应用之间如何进行通讯的问题,虽然有基于 API 的协作,但是并没有对 API 做出具体要求,尤其是基于消息的异步通讯。
目前最常见的应用间通讯就是 RPC 和 HTTP REST API。这两者对于 request/response 的通讯完全没有问题,HTTP REST API 只是有一些性能问题,但是整体还好。不过,这些技术也都在向 Reactive 靠拢,比如 Spring 的 RestTemplate 转向 WebClient,而 gRPC 也有对应的 Reactive gRPC 等。对于新的架构设计,比如 Streaming、Event Driven、IoT 和双向通讯,传统的 RPC 就有点力不从心,而这些正是 RSocket 所擅长的。
RSocket 协议经过非常长时间的论证,当然也借鉴了其他协议的思想,终于形成了目前开发者看到的 1.0.0 版本。RSocket 协议可以说是目前基于二进制异步消息通讯比较完美的版本,克服了之前协议设计中的一些问题,比如 Client/Server 转换到角色对等的方式;Pub/Sub 调整为更灵活的 Request/Stream;元数据和数据分离的方式,提供更灵活的消息路由和消息编码;传输层可插拔,支持 TCP、WebSocket、UDP/Aeron、RDMA 等;同时支持多语言接入,比如 Java、Kotlin、Node、C++、Golang、Python、Rust 等。
目前,RSocket 已经受到广泛支持,Spring Framework 将在 5.2 版本内置 RSocket,Spring Boot 2.2.0 版本也第一位支持 RSocket,其他 Spring Cloud Gateway,Spring Security 等都会支持 RSocket。正如 Linux 基金会战略副总裁 Michael Dolan 所言,Reactive 基于消息驱动的方式可以保证云原生应用所需的弹性,可伸缩性和响应能力,而且这一切都不受其底层基础架构的影响。换言之,利用 Reactive 的特性,开发者不需要依赖云厂商或者 Infra 层就可以达到 Reactive 宣言所说的弹性、可伸缩性和响应能力。可以说,云原生关注应用的外在,比如打包部署、运行环境、监控等,而 Reactive 更关注应用内在,比如代码运行的更有效率,应用间通讯更加自然流畅,一定的弹性和自我恢复能力等。
阿里巴巴 Reactive 实践及开源计划
作为 Reactive 基金会的初创成员之一,阿里巴巴一直给外界的印象都是内部使用了大量 Java 编程语言,积累了大量的 Java 技术栈。但是,据雷卷透露,阿里巴巴内部的一些产品很早就开始进行  Reactive 化 ,比如开发者都了解的 Dubbo,就在做 Reactive 和 RSocket 的整合。但是,Reactive 化的过程还有很多问题需要解决,比如 SDK 的 BUG,适配内部各种架构体系。雷卷认为,最大的问题就是如何将阻塞调整为非阻塞方式,比如数据库操作,非异步的 RPC 调用。
作为基金会的一员,阿里巴巴会将内部 Reactive 化的经验共享给需要的开发者,主要贡献将偏向于 RSocket,主要是分布式 Reactive 与云上的整合,因为阿里巴巴在安全、性能测试、SDK 开发上面都贡献了非常多,同时结合云上的特点以及多租户支持,让云上用户使用更简单。  在内部,阿里巴巴也积极推动产品 Reactive 化,添加对 RSocket 支持等。
在技术革新的同时,阿里巴巴还在着力解决成本问题,如果开发者听过 QCon 2019 北京大会上的 RSocket 演讲,可能会对 Netifi 公司通过研发 RSocket 帮助企业实现微服务,在 40,000RPS 的场景下,Istio 需要每月 3495 美金,而 Netifi 每月只要 388 美金,同时性能提升 10 倍有印象,这对任何企业无疑都极具吸引力。从那次演讲到现在已有半年之久,新推出的 RSocket Broker 已经支持多租户特性,而非独占的方式,成本可再次降低 50% 到 70%,在 4 万 RPS 场景下,每月只要支付 200 美元不到。
虽然阿里巴巴的 Reactive 进程还在进行中,但其带来的成本价值已经有所体现。雷卷表示,主要是同步转异步后,应用的总体性能提升,直接变化就是费用花销变少,这对其他用户而言应该也是一样的。
结束语
对 Node.js 开发者来说,如今几乎都是 Reactive,而对 Java 开发者来说,尤其是后端,目前还处于早期阶段,目前遇到最大的问题是,Reactive 要求用户调整代码,这是最大的麻烦,开发者需要很长时间熟悉、了解和使用。而且相关整合还不完善,虽然 Spring 5.0 已经包含 Reactive,但并不是所有都支持。雷卷表示,Spring 5.2 将从底层全面支持 Reactive,一旦该版本正式发布,开发者就可以体会到 Reactive 和 RSocket 开发的便捷性。
在开源方面,阿里巴巴正在研发 RSocket Broker 产品,希望可以很快推出,让更多开发者能够更好地使用 Reactive,帮助云原生应用解决异步通讯问题,让所有组织和开发者可以更简单、高效、安全,同时极大降低成本。
作者介绍:
雷卷,阿里巴巴资深技术专家,目前就职于阿里巴巴硅谷办公室。在阿里巴巴工作 10 余年,具备 20 年 Java 开发经验,参加过多个内部项目,从物流、旺铺到国际部中间件等。目前主要的研究方式是 Reactive 和 RSocket,将 Reactive 和 RSocket 带到应用开发中,构建更加简单高效的架构设计。雷卷同时也是 RSocket 的开发者,主要着重于 RSocket Broker 的开发。
云计算
2019-09-16 14:25:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
任务(Task)是Spark的最小执行单元,Spark任务是通过Task来执行的。Spark的任务体系是最神秘也是最容易学习的核心模块,任务执行机制点透了那么Spark也就了解的更深入了。Task是任务体系的一个抽象类,有两个子类:ResultTask和ShuffleMapTask,这三个类构成了任务系统的核心。
ResultTask好理解,就是直接执行Task中RDD某个分区的数据操作,还记得之前的RDD的结构吗,里面有一个compute函数,任务就是执行compute函数。
ShuffleMapTask也是执行Task中RDD某个分区的数据操作,所不同的是输出结果的存储方式不一样。ShuffleMapTask会把数据操作的结果保存到类似BlockManager的全局存储中,ShuffleMapTask的结果可供下一个Task作为输入数据。为什么分两种呢?换个说法就很清楚了,ResultTask对应窄依赖的RDD,ShuffleMapTask对应宽依赖的RDD操作(如全连接操作)。ShuffleMapTask需要对数据的读写进行特殊的处理,要用BlockManager来输出数据集的;同样,ShuffleMapTask的子RDD的读取数据集也是从BlockManager来的。
ResultTask和ShuffleMapTask的类的代码非常简单,就是重写runTask方法。
Task通过Task描述对象来反序列化,获得RDD和分区等对象后,创建TaskContextImpl作为任务上下文,然后执行run方法运行任务,读取RDD中的迭代器数据并处理数据。run方法实际是调用子类重写的runTask方法具体执行的。而runTask方法在ResultTask和ShuffleMapTask中被重写。
1、 ResultTask
直接结果任务,这类任务执行完也就完了,其数据不需要被下一个任务再次处理。可以任务是终结者任务。
重写runTask方法。runTask方法的核心代码如下: override def runTask(context: TaskContext): U = { val ser = SparkEnv.get.closureSerializer.newInstance() val (rdd, func) = ser.deserialize[(RDD[T], (TaskContext, Iterator[T]) => U)]( ByteBuffer.wrap(taskBinary.value), Thread.currentThread.getContextClassLoader) func(context, rdd.iterator(partition, context)) }
反序列化得到RDD中定义的数据处理函数func,func符合格式:
(TaskContext, Iterator[T]) => U
然后执行:
func(context, rdd.iterator(partition, context))
这方法的意思就是对rdd分区的数据迭代器轮询,每次取出一条数据执行func操作。ResultTask的重写部分就是这么简单。
2、ShuffleMapTask
ShuffleMap格式的任务,这类任务的执行结果是要被下一个RDD消费的,因此输出数据需要写出到Shuffle区域。Shuffle区域会在分区数据管理中详细的介绍。
重写runTask方法。runTask方法的核心代码如下: override def runTask(context: TaskContext): MapStatus = { val ser = SparkEnv.get.closureSerializer.newInstance() val rddAndDep = ser.deserialize[(RDD[_], ShuffleDependency[_, _, _])]( ByteBuffer.wrap(taskBinary.value), Thread.currentThread.getContextClassLoader) val rdd = rddAndDep._1 val dep = rddAndDep._2 dep.shuffleWriterProcessor.write(rdd, dep, partitionId, context, partition) }
前半段和Result类似,反序列化得到RDD和分区,以及依赖分区dep。然后迭代rdd中的数据并写入到依赖dep的shuffle区域中。
Spark的任务的执行过程这里就说的很明白了,理解了这点,如果再搞清楚了Spark如何分配任务到不同机器上执行的过程,那么可以说Spark的精髓也就掌握的清清楚楚了!是不是信心大增?
云计算
2019-09-12 18:05:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
作者 | 阿里巴巴资深技术专家  雷卷,GitHub ID @linux-china **导读:**在 Python、JavaScript 等一众编程语言崛起风靡之际,一代霸主 Java 风采虽不及当年,但仍横扫了各大编程语言排行榜,依旧是各大企业级应用开发语言中的 NO.1。从 Java 8 之后,Java 引入了很多有用的新语言特性,以及新工具和性能改善。但是仍有非常多的同学在日常开发中没有切换到 Java 8 的后续版本。本篇文章将侧重开发方向,为大家介绍后 Java 8 时代的特性。
首先我们必须承认,Java 8 是一个里程碑式的版本,这个相信大多数Java程序员都认同,其中最知名的是 Streams & Lambda ,这让 Functional Programming 成为可能,让 Java 焕发新的活力。这也是即便 Oracle 不在支持 Java 8 的更新,各个云厂商还是积极支持,站点为 https://adoptopenjdk.net/ ,可以让 Java 8 能继续保留非常长的时间。
目前非常多的同学日常开发并没有切换到 Java 8 后续的版本,所以这篇文章,我们打算写一个后 Java 8 时代的特性,主要是偏向于开发的,不涉及 GC , Compiler , Java Module , Platform 等,如果一一解释,估计非常长的文章,当然后续可以写另外文章介绍。下面的这些特性会影响到我们日常的代码编写。
考虑到 Java 13 马上发布,所以版本覆盖从 9 到 13 ,与此同时 Java Release 的方式调整,一些特性是在某一版本引入(preview),后续收到反馈后做了非常多的增强和完善,这里就不一一说明特性是哪个版本的,你可以理解为后Java 8版本后的特性大杂烩。参考资料来源于官方 features 和 pluralsight 上每一个版本的 Java 特性介绍。
var 关键字(局部变量类型推导) Local-Variable Type Inference
Java 支持泛型,但是如果类型非常长,你又不是特别关注,你用 var 关键字就可以啦,可以让你代码非常简洁。Java IDE 都非常好地支持 var,不用担心代码提示等问题。 Map>> store = new ConcurrentHashMap>>(); Map>> store = new ConcurrentHashMap<>(); Map>> store = new ConcurrentHashMap>>(); //lambda BiFunction function1 = (var s1, var s2) -> s1 + s2; System.out.println(function1.apply(text1, text2));
复制 confd 文件到 bin 目录下,启动 confd。 sudo cp bin/confd /usr/local/bin confd
实际的使用中还有一些小的限制,如 null 赋值问题等,但是这些不是什么问题,马上用起来。
ProcessHandle
虽然我们很少在 Java 中调用系统命令,但是偶尔用到也是有的,当然都是ProcessBuilder 。还有一个就是增强的 ProcessHandle ,可以了解其他进程的一些信息,如获取所有进程、某一进程的启动的命令、启动时间等等。 ProcessHandle ph = ProcessHandle.of(89810).get(); System.out.println(ph.info());
Collection factory methods
创建 ArrayList , HashSet 还是用 new 方法,有点过时啦,直接使用工厂方法就可以啦。 Set ints = Set.of(1, 2, 3); List strings = List.of("first", "second");
String 类的新 API
这里没法一一列举,说几个重要的 ,了解后就不需要第三方的 StringUtils 啦。repeat, isEmpty, isBlank, strip, lines, indent, transform, trimIndent, formatted 等。
HTTP 2 支持
当然如果你使用 OkHTTP 3 那就没有问题,如果你不想引入其他开发包,那么 Java 已经支持 HTTP 2 啦,代码基本也差不多,当然同步和异步都支持。 HttpClient client = HttpClient.newHttpClient(); HttpRequest req = HttpRequest.newBuilder(URI.create("https://httpbin.org/ip")) .header("User-Agent", "Java") .GET() .build(); HttpResponse resp = client.send(req, HttpResponse.BodyHandlers.ofString()); System.out.println(resp.body());
Text Block(JDK 13)
在之前版本,你要有一大段文本,你要对双引号进行转换,转换后非常不适合阅读,如下: String jsonText = "{"id": 1, "nick": "leijuan"}"; 新的方式 text block: //language=json String cleanJsonText = """ {"id": 1, "nick": "leijuan"}""";
简单多啦,你可以自由写代码,不用担心各种双引号转换的问题,copy分享转换等。稍等,你为何要在 cleanJsonText 前面添加 //language=json ,这个什么鬼?这个是 IntelliJ IDEA 的一个特性,你的 text block 还是有语义的,如是一段HMTL、JSON、SQL 等,添加这个后,马上就代码提示啦。一般人我不告诉他 :)
text block 还有一个小特性就是基本的模板特性支持,你在text block中要引入一些上下文变量,直接 %s ,然后调用 formatted 方法就可以啦。 //language=html String textBlock = """ Hello %s"""; System.out.println(textBlock.formatted(nick));
Switch 提升
Arrow Labels
接入了 "->" switch 箭头,不需要写那么多 break 啦,代码如下: //legacy switch (DayOfWeek.FRIDAY) { case MONDAY: { System.out.println(1); break; } case WEDNESDAY: { System.out.println(2); break; } default: { System.out.println("Unknown"); } } //Arrow labels switch (DayOfWeek.FRIDAY) { case MONDAY, FRIDAY, SUNDAY -> System.out.println(6); case TUESDAY -> System.out.println(7); case THURSDAY, SATURDAY -> System.out.println(8); case WEDNESDAY -> System.out.println(9); }
Switch Expressions
也就是 switch 可以有返回值啦,代码如如下: //Yielding a value int i2 = switch (DayOfWeek.FRIDAY) { case MONDAY, FRIDAY, SUNDAY -> 6; case TUESDAY -> 7; case THURSDAY, SATURDAY -> 8; case WEDNESDAY -> 9; default -> { yield 10; } };
关键字 yield 表示 switch 表达式的返回值。
我想马上使用这些特性
你说的这么多,都非常不错,但是我们线上还是 Java 8 环境,有什么用?也就看看而已。不用担心,有人也想到啦。 这个 项目 ,支持将 JDK 12+ 的各种语法能够透明编译到 Java 8 的 VM 上,也就是你现在用这些语法特性跑在 Java 8 上完全没有问题,所以即便是 Java 8 的环境,没有问题,以上的特性都可以使用。
如何使用?非常简单。
首先下载最新的 JDK,如 JDK 13 ,然后在依赖中添加 jabel-java-plugin。 com.github.bsideup.jabel jabel-javac-plugin 0.2.0
然后调整一下 maven 的 compiler plugin,将 source 设置为你想要的 Java 版本,如 13 , target 和 release 设置为 8 就可以啦。 IntelliJ IDEA 会自动识别,也不需要调整。 org.apache.maven.plugins maven-compiler-plugin 3.8.1 13 8 8
这样你就可以愉快地使用介绍的特性啦。
总结
如果有一些特性没有整理,而且非常有用的,大家反馈一下,如 API 的调整等,方便后续同学参考一下。
扫描下方二维码添加小助手,与 8000 位云原生爱好者讨论技术趋势,实战进阶!
进群暗号:公司-岗位-城市
云计算
2019-09-10 18:15:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
问题:
怎么让列表数据背景色根据数据条件变色?
解决方法:
样式编辑器定义好颜色
渲染前事件,根据条件return要渲染的颜色。
加入JEPaaS技术交流群,了解更多
云计算
2019-08-30 15:53:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
本文作者:用户_123456789
百度地图Android SDK是一套应用于Android 4.0及以上版本的地图能力应用程序接口,开发者可以轻松、快捷地在自己的Android应用中集成。目前已经应用于网约车,共享出行,外卖,快递物流等众多行业。 功能包括:地图展示 (普通地图、卫星图、路况图、热力图、室内图、个性化地图) 与地图交互 (手势交互、控件交互、方法交互) 在地图上绘制 (标记点、绘制线、绘制面、绘制Overlay等) 检索地图数据 (检索POI、公交信息检索、地理编码检索、行政区域检索等) 路线规划 (步行路线规划、骑行路线规划、驾车路线规划、跨城路线规划、公交路线规划)等功能。 一
Step 1 注册和获取密钥(AK)

用户在使用SDK之前需要获取百度地图移动版开发密钥(AK),该AK与百度账户相关联。 开发者必须先有百度帐户,才能获得AK。并且,该AK与引用SDK的程序包名有关,具体流程请参照申请密钥。请妥善保存AK,地图初始化时需要用到AK。创建好的AK会永久保存在控制台。 注册账号 点击控制台创建一个应用
填写应用名称、应用类型注意选择“Android SDK”,正确填写安全码,点击确认,系统将会自动帮您生成相应的开发密钥:

获取安全码 申请密钥开发需知: 安全码的组成规则为:Android签名证书的SHA1值+packagena 同一个AK中,可以填写开发版SHA1和发布版SHA1,这样App从开发、测试到发布整个过程中均不需要改动AK。 此功能完全兼容以前的AK,默认将原有的SHA1放在发布版SHA1上,开发者也可自己更新,将原有的开发版本的AK和发布版本的AK对应的SHA1值合并后使用。 调试版本(debug)和发布版本(release)下的SHA1值是不同的,发布apk时需要根据发布apk对应的keystore重新配置Key。 获取SHA1值 获取SHA1因不同的开发工具(Eclipse/Android Studio)不同电脑系统(Windows/Mac)获取的方式也不相同,执行的命令是相同的,在Windows系统下或者Eclipse开发工具下请参考官网的获取方式(点击文章下方阅读原文),以下使用的是mac系统studio开发工具,获取调试版SHA1值为列。
(1) 打开终端工具:输入cd .android,定位到.android文件夹下

(2)调试版本使用 debug.keystore,命令为:keytool -list -v -keystore debug.keystore
发布版本使用apk对应的 keystore,命令为:keytool -list -v -keystore apk的keystore

(3)提示输入密钥库密码,调试版本默认密码是: android,发布模式的密码是为apk的keystore设置的密码。输入密钥后回车(如果没设置密码,可直接回车),此时可在控制台显示的信息中获取SHA1值,如下图所示:
获取包名 Android应用获取包名packagename,根据开发工具不同,获取位置有所不同,Eclipse请参考官网获取方式(点击文章下方阅读原文),以下使用Android Studio获取 开发包名需要在文件build.gradle中查询applicationId,并确保applicationId与在AndroidManifest.xml中定义的包名一致 在文件build.gradle中查询applicationId,方法如图:
注意:使用Android Studio开发,如遇到applicationId与在AndroidManifest.xml中定义的包名不一致的情况,以appclicationid为准。 完成以上步骤点击确定可获取到AK值如图所示:
申请完AK以后接下来就需要在AndroidManifest.xml文件中的application中配置AK如图所示:

android:name="com.baidu.lbsapi.API_KEY"
android:value="开发者 key" />



Step 2 下载百度地图SDK
下载地图SDK有两种方式 一键下载(下载所有百度地图SDK功能) 自定义下载(结合自身需求、自定义选择业务功能,打包下载所选功能开发包)
自定义下载完成后的文件格式
(1) BaiduLAB_Android.jar文件就是包含了所需要的所有功能的jar包。
(2) armeabi等文件夹里就是针对不同手机CPU架构的.so文件。



Step 3 Android Studio工程配置
添加jar包 将下载的地图SDK的jar包复制到工程的libs目录下,如图所示
方法一:
工程配置还需要把jar包集成到自己的工程中,如上图所示,放入libs目录下。对于每个jar文件,右键-选择Add As Library,导入到工程中。同时在build.gradle中会生成工程所依赖的对应的jar文件说明,代码如下所示:
方法二:
1)菜单栏选择 File —>Project Structure。
2)在弹出的Project Structure对话框中, 选择左侧列表module的app, 然后点击Dependencies选项卡。
3)点击绿色的加号选择Jar dependency然后选择要添加的jar包即可完成上边的操作,完成后在APP目录下的build.gradle文件中,会有引入的类库,如上述代码所示。
添加so库 现在Android兼容 5 种CPU架构: armeabi、armeabi-v7a、arm64-v8a、x86、x86_64。开发者可根据实际使用需求,放置所需.so到对应的工程文件夹内。
方法一:
使用默认配置,不需要修改build.gradle,在src/main/目录下新建jniLibs目录,工程会自动加载src目录下的.so动态库,如果已有这个目录,可以直接将.so文件拷贝到对应的架构下,如下图目录结构所示。


方法二:
使用自定义配置,但这样工程并不会自动加载libs下的.so,需说明.so的路径为该libs路径,关联所有地图SDK的.so文件,即在APP文件夹下的bulid.gradle加入代码。
(1) 将下载文件的armeabi文件夹复制到libs目录。

(2) 打开build.gradle,找到sourceSets标签,在里面增加一项配置,如图所示:

sourceSets {
main {
jniLibs.srcDir 'libs'
}
}
添加所需权限 使用地图SDK之前,需要在AndroidManifest.xml 文件中进行相关权限设置,确保地图功能可以正常使用。
注意: 权限应添加在appliction之外,如添加到appliction内部,会导致无法访问网络,不显示地图。



//获取设备网络状态,禁用后无法获取网络状态

//网络权限,当禁用后,无法进行检索等相关业务

//读取设备硬件信息,统计数据

//读取系统信息,包含系统版本等信息,用作统计

//获取设备的网络状态,鉴权所需网络代理

//允许sd卡写权限,需写入地图数据,禁用后无法显示地图

//获取统计数据

//鉴权所需该权限获取进程列表

//使用步行AR导航,配置Camera权限 配置AK 在AndroidManifest.xml的application中添加开发密钥AK。如果还没有获取AK,请按照Step1流程来获取。

android:name="com.baidu.lbsapi.API_KEY"
android:value="开发者 key" />



Step 4 Hello BaiduMap
显示基础地图
百度地图SDK支持两种地图组件分别是MapView和TextureMapView,是Android View类的子类,MapView和TextureMapView都是地图容器。用这两种加载地图的方法与Android提供的其他View 一样,用于在Android View中放置地图。两个地图组件的使用方法是一样的,具体的使用步骤如下: 在布局xml文件中添加地图控件;
android:id="@+id/bmapView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:clickable="true" /> 在应用程序创建时初始化SDK引用的Context全局变量;
注意:地图的各个组件功能依赖与SDK的正确初始化,并且为了保证整个APP的生命周期里地图SDK都存活、功能可用,我们建议该方法在APP的Applcaition派生类的onCreate方法中调用。

public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//在使用SDK各组件之前初始化context信息,传入ApplicationContext
//注意该方法要再setContentView方法之前实现
SDKInitializer.initialize(getApplicationContext());
setContentView(R.layout.activity_main);
}
} 创建地图Activity,管理地图生命周期;
public class MainActivity extends Activity {
private MapView mMapView = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//在使用SDK各组件之前初始化context信息,传入ApplicationContext
//注意该方法要再setContentView方法之前实现
SDKInitializer.initialize(getApplicationContext());
setContentView(R.layout.activity_main);
//获取地图控件引用
mMapView = (MapView) findViewById(R.id.bmapView);
}
@Override
protected void onDestroy() {
super.onDestroy();
//在activity执行onDestroy时执行mMapView.onDestroy(),实现地图生命周期管理
mMapView.onDestroy();
}
@Override
protected void onResume() {
super.onResume();
//在activity执行onResume时执行mMapView. onResume (),实现地图生命周期管理
mMapView.onResume();
}
@Override
protected void onPause() {
super.onPause();
//在activity执行onPause时执行mMapView. onPause (),实现地图生命周期管理
mMapView.onPause();
}
}
` 注:自v4.5.2起,mapview已经可以完美替代textureMapview,且性能更好。textureMapview当前版本仍会保留,但不建议使用。 如果使用TextureView渲染(使用前提:Android 4.0以上系统,并开启强制GPU渲染)。 完成以上步骤就可以在你的APP中显示出地图。



---------常见错误--------- 鉴权错误码230:我们对Mobile类型(Android/IOS)的服务请求进行了安全码校验;所谓安全码即开发者在API控制台申请AK(AK和APP一一对应)时提供的APP签名的SHA1+”;”+包名;请求服务必须要携带该安全码作为参数,不携带或者携带不一致的安全码给服务端,均会返回230错误。当出现230错误时。解决办法:请开发者先查看一下APP当前签名的SHA1值(注意不是MD5)和包名,然后去API控制台把查看该AK对应配置的安全码是否和APP实际的一致,如果不一致请去API控制台手动修改一致即可。 错误码-11:如果遇到错误码是errorcode: -11 uid: -1 appid -1 msg: httpsPost failed; 这种情况下是由系统的时间不是当前时间造成的,解决办法:把时间修改成自动获取时间和日期,也就是使用网络提供的时间。 错误码-10:如果遇到错误码是errorcode: -10 uid: -1 appid -1 msg: Current network is not available;(1)这种情况下一般都是由网络原因造成的鉴权失败,解决办法:检查网络是不是正常。(2)网络正常,还是出现这种情况,请打开浏览器排查下能不能正常访问https请求的地址,如不能正常访问则说明您的https安全证书到期,需要网站所有者到https安全证书签发机构CA续签证书。 错误码-1:如果遇到错误码是errorcode: -1 uid: -1 appid -1 msg: Exception:java.security.cert.CertPathValidatorException: Trust anchor for certification path not found;这种情况下是由https证书过期造成的,解决办法:请打开浏览器排查下能不能正常访问https请求的地址,如不能正常访问则说明您的https安全证书到期,需要网站所有者到https安全证书签发机构CA续签证书。 错误码-200:如果遇到错误码是errorcode: 200 uid: -1 appid -1 msg: APP不存在;这种情况一般都是AndroidManifest.xml文件中key没有填写正确。解决办法:请仔细核对AndroidManifest.xml的key和控制台的key是否一致,检查AndroidManifest.xml中填写key时是否有空格存在。 错误:NativeLoader: found libBaiduMapSDK_map_v5_1_0.so error 这种情况是因为缺少.so文件。解决办法:请按照官网开发引导去配置,也可以参考官网中的demo进行配置。 错误:NativeLoader: loadException; NativeLoader: BaiduMapSDK_base_v5_1_0 Failed to load. 这种情况是so库加载失败,禁止不同架构下的.so文件串用。解决办法:请保证.so文件是在对应的架构文件夹下。

原文链接地址: https://developer.baidu.com/topic/show/290280
云计算
2019-08-28 11:45:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
本文作者:yanxin1563
本文作者:
kanglei
一、背景
百度App自2016年上半年尝试Feed流业务形态,至2017年下半年,历经10个版本的迭代,基本完成了产品形态的初步探索。在整个Feed流形态的闭环中,新闻详情页(文中称为落地页)作为重要的组成部分,如果打开页面后,loading时间过长,会严重影响用户体验。因此我们针对落地页这种H5的首屏展现速度进行了长期优化,本文会详细阐述整个优化思路和技术细节
二、方法论
通过分析用户反馈,发现当时的落地页从点击到首屏展现平均需要3s的时间,每次用户兴致勃勃的想要浏览感兴趣的文章时,却因为过长的loading时间,而不耐烦的选择了back。为了提升用户体验,我们进行了以下工作:
通过用户反馈、QA测试等多种渠道,发现落地页首屏加载慢问题 定义首屏性能指标(首屏含图,以图片加载为准;首屏无图,以文字渲染结束为准) NA、内核、H5三方针对自己加载H5的流程进行划分并埋点上报 统计侧根据三端上报的数据产出平均值、80分位值的性能报表 分析性能报表,找到不合理的耗时点,并进行优化 以AB实验方式,对比优化前后的性能报表数据,产出优化效果,同时评估用户体验等相关指标 按照长期优化的方式,不断分析定位性能瓶颈点并优化,以AB实验方式评估效果,最终达到我们的落地页秒开目标
三、Hybrid方案简述及性能瓶颈
(一)方案简述
优化之前,我们与业内大多数的App一样,在落地页的技术选型中,为了满足跨平台和动态性的要求,采用了Hybrid这种比较成熟的方案。Hybrid,顾名思义,即混合开发,也就是半原生半Web的方式。页面中的复杂交互功能采用端能力的方式,调用原生API来实现。成本低,灵活性较好,适合偏信息展示类的H5场景
下面用一张图来表示百度App中Hybrid的实现机制和加载流程
(二)性能瓶颈
为了分析Hybrid方案首屏展现较慢的原因,找到具体的性能瓶颈,客户端和前端分别针对各自加载过程中的关键节点进行埋点统计,并借由性能监控平台日志进行展示,下图是截取的某一天全网用户的落地页首屏展现速度80分位数据
各阶段性能点可以按Hybrid加载流程进行划分,可以看到,从点击到首屏展现,大致需要2600ms,其中初始化NA组件需要350ms,Hybrid初始化需要170ms,前端H5执行JS获取正文并渲染需要1400ms,完成图片加载和渲染需要700ms的时间
我们具体分析下四个阶段的性能损耗主要发生在哪些地方:
1) 初始化NA组件
从点击到落地页框架初始化完成,主要工作为初始化WebView,尤其是第一次进入(WebView首次创建耗时均值为500ms)
2) Hybrid初始化
这个阶段的工作主要包含两部分,一个是根据调起协议中传入的相关参数,校验解压下发到本地的Hybrid模板,大致需要100ms的时间;此外,WebView.loadUrl执行后,会触发对Hybrid模板头部和Body的解析
3) 正文加载&渲染
执行到这个阶段,内核已经完成了对Hybrid模板头部和body的解析,此时需要加载解析页面所需的JS文件,并通过JS调用端能力发起对正文数据的请求,客户端从Server拿到数据后,用JsCallback的方式回传给前端,前端需要对客户端传来的JSON格式的正文数据进行解析,并构造DOM结构,进而触发内核的渲染流程;此过程中,涉及到对JS的请求,加载、解析、执行等一系列步骤,并且存在端能力调用、JSON解析、构造DOM等操作,较为耗时
4) 图片加载
第(3)步中,前端获取到的正文数据包含落地页的图片地址集,在完成正文的渲染后,需要前端再次执行图片请求的端能力,客户端这边接收到图片地址集后按顺序请求服务器,完成下载后,客户端会调用一次IO将文件写入缓存,同时将对应图片的本地地址回传给前端,最终通过内核再发起一次IO操作获取到图片数据流,进行渲染;
总体来看,图片渲染的时间依赖前端的解析效率、端能力执行效率、下载速度、IO速度等因素
通过分析,延伸出对Hybrid方案的一些思考: 渲染为什么这么慢 图片请求能否提前 串行逻辑是否可以改为并行 WebView初始化时间是否还可以优化
四、百度App落地页优化方案
(一)CloudHybrid
基于之前对Hybrid性能的分析,我们内部孵化了一个叫做CloudHybrid的项目,用来解决落地页首屏展现慢的痛点;一句话来形容CloudHybrid方案,就是采用后端直出+预取+拦截的方式,简化页面渲染流程,提前化&并行化网络请求逻辑,进而提升H5首屏速度
1.后端直出-快速渲染首屏
a. 页面静态直出
对于Hybrid方案来说,端上预置和加载的html文件只是一个模板文件,内部包含一些简单的JS和CSS文件,端上加载HTML后,需要执行JS通过端能力从Server异步请求正文数据,得到数据后,还需要解析JSON,构造DOM,应用CSS样式等一系列耗时的步骤,最终才能由内核进行渲染上屏;为了提升首屏展示速度,可以利用后端渲染技术(smarty)对正文数据和前端代码进行整合,直出首屏内容,直出后的html文件包含首屏展现所需的内容和样式,内核可以直接渲染;首屏外的内容(包括相关推荐、广告等)可以在内核渲染完首屏后,执行JS,并利用preact进行异步渲染, 百度APP直出方案:
对于客户端来说,从CDN中拉取到的html都是已经在server渲染好首屏的,这样的内容无需二次加工,展现速度可以大大提升,仅直出一点,手百Feed落地页的首屏性能数据就从2600ms优化到2000ms以内
b. 动态信息回填
为了保证首屏渲染结果的准确性,除了在server侧对正文内容和前端代码进行整合外,还需要一些影响页面渲染的客户端状态信息,例如首图地址、字体大小、夜间模式等
这里我们采用动态回填的方式,前端会在直出的html中定义一系列特殊字符,用来占位;客户端在loadUrl之前,会利用正则匹配的方式,查找这些占位字符,并按照协议映射成端信息;经过客户端回填处理后的html内容,已经具备了展现首屏的所有条件
c. 动画间渲染
先看下优化前后效果,第一幅是优化前,第二幅是优化后 :


正常来说,直出后的页面展现速度已经很快了;但在实际开发中,你可能会遇到即使自己的数据加载速度再快,仍然会出现Activity切换过程中无法渲染H5页面的问题(可以通过开发者模式放慢动画时间来验证),产生视觉上的白屏现象(如上面优化前图)
我们通过研究源码发现,系统处理view绘制的时候,有一个属性setDrawDuringWindowsAnimating,从命名可以看出来,这个属性是用来控制window做动画的过程中是否可以正常绘制,而恰好在Android 4.2到Android N之间,系统为了组件切换的流程性考虑,该字段为false,我们可以利用反射的方式去手动修改这个属性,改进后的效果见上面优化后图 /** * 让 activity transition 动画过程中可以正常渲染页面 */ private void setDrawDuringWindowsAnimating(View view) { if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M || Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) { // 1 android n以上 & android 4.1以下不存在此问题,无须处理 return; } // 4.2不存在setDrawDuringWindowsAnimating,需要特殊处理 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) { handleDispatchDoneAnimating(view); return; } try { // 4.3及以上,反射setDrawDuringWindowsAnimating来实现动画过程中渲染 ViewParent rootParent = view.getRootView().getParent(); Method method = rootParent.getClass() .getDeclaredMethod("setDrawDuringWindowsAnimating", boolean.class); method.setAccessible(true); method.invoke(rootParent, true); } catch (Exception e) { e.printStackTrace(); } } /** * android4.2可以反射handleDispatchDoneAnimating来解决 */ private void handleDispatchDoneAnimating(View paramView) { try { ViewParent localViewParent = paramView.getRootView().getParent(); Class localClass = localViewParent.getClass(); Method localMethod = localClass.getDeclaredMethod("handleDispatchDoneAnimating"); localMethod.setAccessible(true); localMethod.invoke(localViewParent); } catch (Exception localException) { localException.printStackTrace(); } }
2.智能预取-提前化网络请求
经过直出的改造之后,为了更快的渲染首屏,减少过程中涉及到的网络请求耗时,我们可以按照一定的策略和时机,提前从CDN中请求部分落地页html,缓存到本地,这样当用户点击查看新闻时,只需从缓存中加载即可。手百预取服务架构图:
目前手百预取服务支撑着图文、图集、视频、广告等多个业务方,根据业务场景的不同,触发时机可以自定义,也可以遵循我们默认的刷新、滑停、点击等时机,此外,我们会对预取内容进行优先级排序(根据资源类型、触发时机),会动态的根据当前手机状态信息进行并发控制和流量控制,在一些降级场景中,server还可以通过云控的方式来控制是否预取以及预取的数量
3.通用拦截-缓存共享、请求并行
在落地页中,除了文本外,图片也是重要的组成部分。直出解决了文字展现的速度问题,但图片的加载渲染速度仍不理想,尤其是首屏中带有图片的文章,其首图的渲染速度才是真正的首屏时间点
传统Hybrid方案,前端页面通过端能力调用NA图片下载能力来缓存和渲染图片,虽然实现了客户端和前端图片缓存的共享,但由于JS执行时机较晚,且多次端能力调用存在效率问题,导致图片渲染延后
初步改进方案: 为了提升图片加载速度,减少JS调用耗时,改为纯H5请求图片,速度虽然有所提升,但是客户端和前端缓存无法共享,当点击图片调起NA图片查看器时,无法做到沉浸式效果,且仍需重复下载一次图片,造成流量浪费
终极方案: 借由内核的shouldInterceptRequest回调,拦截落地页图片请求,由客户端调用NA图片下载框架进行下载,并以管道方式填充到内核的WebResourceResponse中
此方案在满足图片渲染速度的同时,解耦了客户端和前端代码,客户端充当server角色,对图片进行请求和缓存控制,保证前端和客户端可以共用图片缓存,改造后的方案,非首图展现流程,页面不卡顿,首屏80分位值缩短80ms~150ms
效果如下,第一幅图为优化前,第二幅为优化后:

4.整体方案流程
(二)新的优化尝试
1.WebView预创建
为了减少WebView的性能损耗,我们可以在合适时机提前创建好WebView,并存入缓存池,当页面需要显示内容时,直接从缓存池获取创建好的WebView,根据性能数据显示,WebView预创建可以减少首屏渲染时间200ms+
具体以Feed落地页为例,当用户进入手百并触发Feed吸顶操作后,我们会创建第一个WebView,当用户进入落地页后,会从缓存池中取出来渲染H5页面,为了不影响页面的加载速度,同时保证下次进入落地页缓存池中仍然有可用的WebView组件,我们会在每次页面加载完成(pageFinish)或者back退出落地页的时机,去触发预创建WebView的逻辑
由于WebView的初始化需要和context进行绑定,若想实现预创建的逻辑,需要保证context的一致性,常规做法我们考虑可以用fragment来实现承载H5页面的容器,这样context可以用外层的activity实例,但Fragment本身的切换流畅度存在一定问题,并且这样做限定了WebView预创建适用的场景。为此,我们找到了一种更加完美的替代方案,即MutableContextWrapper Special version of ContextWrapper that allows the base context to be modified after it is initially set. Change the base context for this ContextWrapper. All calls will then be delegated to the base context. Unlike ContextWrapper, the base context can be changed even after one is already set.
简单来说,就是一种新的context包装类,允许外部修改它的baseContext,并且所有ContextWrapper调用的方法都会代理到baseContext来执行
下面是截取的一段预创建WebView的代码: /** * 创建WebView实例 * 用了applicationContext */ @DebugTrace public void prepareNewWebView() { if (mCachedWebViewStack.size() < CACHED_WEBVIEW_MAX_NUM) { mCachedWebViewStack.push(new WebView(new MutableContextWrapper(getAppContext()))); } } /** * 从缓存池中获取合适的WebView * * @param context activity context * @return WebView */ private WebView acquireWebViewInternal(Context context) { // 为空,直接返回新实例 if (mCachedWebViewStack == null || mCachedWebViewStack.isEmpty()) { return new WebView(context); } WebView webView = mCachedWebViewStack.pop(); // webView不为空,则开始使用预创建的WebView,并且替换Context MutableContextWrapper contextWrapper = (MutableContextWrapper) webView.getContext(); contextWrapper.setBaseContext(context); return webView; }
2.NA组件懒加载
a. WebView初始化完成,立刻loadUrl,无需等待框架onCreate或者OnResume结束
b. WebView初始完成后到页面首屏绘制完成之间,尽量减少UI线程的其他操作,繁忙的UI线程会拖慢WebView.loadUrl的速度 具体到Feed落地页场景,由于我们的落地页包含两部分,WebView+NA评论组件,正常流程会在WebView初始化结束后,开始评论组件的初始化及评论数据的获取。由于此时评论的初始化仍处在onCreate的UI消息处理中,会严重延迟内核加载主文档的逻辑。考虑到用户进入落地页的时候,评论组件对用户来说并不可见,所以将评论组件的初始化延迟到页面的pageFinish时机或者firstScreenPaintFinished; 80分位性能提升60ms~100ms
3.内核优化
a. 内核渲染优化:
内核中主要分为三个线程(IOThread、MainThread、ParserThread),首先IOThread会从网络端或者本地获取html数据,并把数据交给MainThread(渲染线程,十分繁忙,用于JS执行,页面布局等),为了保证MainThread不被阻塞,需要额外起一个后台线程(ParserThread)用来做html的解析工作。ParserThread每解析到落地页html中带有特殊class标记的一个div标签或者P标签(图中的first、second)时,就会触发一次MainThread的layout工作,并把layout后得到的高度与屏幕高度进行对比,如果当前layout高度已经大于屏幕高度,我们认为首屏内容已经完成布局,可以触发渲染上屏逻辑,不必等到整篇html全部解析完成再上屏,提前了首屏的渲染时间; 80分位下,内核的渲染优化可以提升首屏速度100ms~200ms
b. 预加载JS:
预创建好WebView后,通过预加载JS(与内核约定好的JS内容,内核侧执行该JS时,只做初始化操作),触发WebView初始化逻辑,缩短后续加载url耗时;80分位性能提升80ms左右
五、新的问题-流量和速度的平衡
频繁预取会带来流量的浪费:预取的命中率虽然达到了90%以上,但有效率仅有15%
解决思路: 压缩预取的包大小,减少下行流量 少预取或者不预取
(一)精简预取数据:
图文:优化直出html中内联的css、icon等数据,数据大小减少约40%

(二)后端智能预取:
1) 图文:通过对图文资源进行评分,来决定4G是否需要预取,多组AB试验最优效果劣化9.5ms
2)视频:为了平衡性能和流量,在性能劣化可接受的范围内(视频起播时间劣化100ms),针对视频部分采用流量高峰期不预取的策略,减少视频总流量约7%,整体带宽峰值下降3%
(三)AI智能预取
通用用户操作行为,对Feed预取进行AI预测,减少无效预取的数量。
六、总结&展望
(一)优化总结
在总结之前,先来看下整体优化的前后效果对比,第一幅为优化前,第二幅为优化后:

可以看到,经过一系列的优化手段,落地页已经实现了秒开效果。回顾所做的事情,从分析用户反馈到定位性能瓶颈,再到各种优化尝试,发现所有类似的性能优化手段都可以从以下几点入手: 提前做:包括预创建WebView和预取数据 并行做:包括图片直出&拦截加载,框架初始化阶段开启异步线程准备数据等 轻量化:对于前端来说,要尽量减少页面大小,删减不必要的JS和CSS,不仅可以缩短网络请求时间,还能提升内核解析时间 简单化:对于简单的信息展示页面,对内容动态性要求不高的场景,可以考虑使用直出替代hybrid,展示内容直接可渲染,无需JS异步加载
(二)TODO 页面的更新机制,目前方案仅适用于偏静态页面,对于动态性要求较高的业务,需要提供页面更新机制,保证每次显示的正确性 开源之路:后续计划将我们总结下来的这套方案打包开源,前行之路必定坎坷,希望大家多多支持
---------------------------------
在微信-搜索页面中输入“ 百度App技术 ”,即可关注微信官方账号。
原文链接地址: https://developer.baidu.com/topic/show/290272
云计算
2019-08-25 15:47:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
本文将解读Rainbond集群的安装和运维的原理,使用户基本了解Rainbond的安装机制和运维重点,便于用户搭建大型Rainbond集群。
1.Rainbond集群节点概述
1.1 节点分类
属性 类型 说明 manage 管理节点 集结平台自身组件,提供应用构建、调度、管理等功能,提供数据中心基础服务与API接口,充当控制集群的角色。
gateway compute
网关节点 计算节点
集群内应用被外网访问的流量入口和负载均衡器,提供HTTP, HTTPs路由, TCP/UDP服务, 负载均衡器, 高级路由(A/B测试, 灰度发布)等功能。 提供应用运行的计算资源,N个计算节点组成计算资源池供给管理节点灵活调度。
1.2 节点部署主要服务组件概述
角色 组件 说明 rbd-dns 提供本地dns服务,服务于集群内应用的DNS解析。
etcd 管理节点etcd
kube-controller-manager Kubernetes管理组件之一, Pod编排器
rbd-webcli 提供应用web方式进入容器命令行的服务
nfs_server 远程存储挂载
rbd-hub 基于Docker Registry封装,提供Docker镜像存储服务,服务于数据中心内部
kube-scheduler Kubernetes管理组件之一,Pod调度器
docker 应用容器引擎
rbd-mq 消息队列服务
calico 集群SDN服务,为应用提供网络支持
rbd-chaos 应用构建服务,提供源码,Docker镜像等方式持续构建应用。
rbd-worker 应用运行控制器
kube-apiserver Kubernetes管理组件之一, 提供API服务
rbd-eventlog Rainbond 事件处理与日志汇聚服务
rbd-monitor Rainbond 监控管理服务,基于Prometheus封装
rbd-api Rainbond API服务,数据中心控制层面的入口。
rbd-db Rainbond 数据库服务,支持MySQL,Tidb与CockroachDB
rbd-app-ui 应用控制台web服务
rbd-repo 源码构建仓库服务,基于Artifactory OSS封装
node Rainbond 集群和节点控制器服务
etcd-proxy 计算节点etcd-proxy
rbd-dns Rainbond内部dns服务,与管理节点DNS服务共同对当前节点的应用提供DNS解析
kubelet Kubernetes 计算负载节点组件
docker 应用容器引擎
calico 集群SDN服务,为应用提供网络支持
node Rainbond节点控制器,提供服务守护、自动运维、日志收集、服务发现等服务。
网关节点 docker 应用容器引擎
calico 集群SDN服务,为应用提供网络支持

rbd-dns
rbd-gateway
Rainbond内部dns服务,可作为集群dns服务使用
1.3 节点规划
一个完整的Rainbond集群中必须包含manage、gateway、compute角色的节点和暂不作为Rainbond安装支持的存储节点,当然三种属性可以在同一个节点上组成单节点的Rainbond集群。安装Rainbond之前需要根据企业自身需求合理的规划计算资源,这里主要是指物理机或虚拟机节点。
从上文中列举的主要Rainbond服务组件来综合分析,管理节点的合理规划是关键。
Rainbond的主要数据存储组件是: Etcd
根据Etcd集群组建特性,其必须部署为1,3,5奇数节点。 Mysql
Mysql数据库的部署模式主要有主从、多主等模式, Rbd-monitor(Prometheus)
Prometheus具有单机自治特性,因此每一个Rbd-monitor节点都是独立的数据采集和存储,基本上可以认为多节点数据是一致的。
Rainbond安装脚本对Etcd,Rbd-monitor做了较好的自动安装支持,对于Mysql数据库,我们更建议用户独立安装Mysql数据库并提供给Rainbond安装脚本。管理节点其他的组件基本上可以认为是无状态的,或有状态的组件都自身实现了良好的工作节点选举。对部署节点数无关键要求。因此我们推荐的管理节点数量是3个及以上。
网关节点处理流量入口,每一个Rainbond节点目前都独立提供了所有访问策略的支持,因此上层可以采用4层负载均衡策略或VIP策略,因此我们推荐的节点数量是2个及以上。
计算节点提供计算负载,节点越多,集群计算容量越大,因此计算节点的规划取决于集群需要运行的应用数量,随时可以增加或下线节点。因此我们推荐的节点数量是2个及以上。
2. 安装原理说明
Rainbond-Ansible 项目是Rainbond子项目之一,提供Rainbond集群便捷的安装支持,采用Ansible自动化部署框架实现。其具有安装简单、工作原理简单、模块化、生态完善等特点。 早期我们采用了 SaltStack 实现,其工作模式复杂,不透明的节点通信机制。Rainbond安装过程受限于SaltStack的稳定性,因此我们从5.0版本后对安装脚本进行了重构。
2.1 安装脚本结构 . ├── callback_plugins # 任务失败时打印帮助消息回调插件 │ └── help.py # 回调插件示例 ├── hack # 部署本地资源文件目录 │ ├── chinaos # 操作系统的安装包源 │ │ ├── CentOS-Base.repo # CentOS的源 │ │ ├── centos-release # CentOS的全局配置 │ │ ├── sources.list # Ubuntu的源 │ │ ├── ubuntu-lsb-release # Ubuntu的版本配置 │ │ └── ubuntu-release # Ubuntu的全局配置 │ ├── docker # Docker部署资源文件目录 │ │ ├── get-docker.sh # 快速部署Docker脚本 │ │ └── rainspray.list # 快速部署Docker的Ubuntu源 │ ├── files # 好雨工具包 │ │ ├── bin # grctl的二进制文件 │ │ ├── health # 健康监测脚本 │ │ ├── ssh # ssh配置脚本 │ │ └── ssl # 好雨加密证书 │ ├── manifests # 应用配置文件 │ │ ├── dashboard # 仪表盘-配置 │ │ ├── efk # efk-配置 │ │ ├── es-cluster # es集群-配置 │ │ ├── heapster # heapster-配置 │ │ ├── ingress # ingress-配置 │ │ ├── jenkins # jenkins-配置 │ │ ├── metrics-server # metrics-配置 │ │ ├── prometheus # prometheus-配置 │ │ └── storage # storage-配置 │ ├── step # Ansible安装步骤剧本 │ │ ├── 00.prepare.yml # 安装前检测 │ │ ├── 01.docker.yml # docker-配置 │ │ ├── 02.image.yml # image-配置 │ │ ├── 10.etcd.yml # etcd-配置 │ │ ├── 11.kube-master.yml # kube-master-配置 │ │ ├── 12.kube-worker.yml # kube-worker-配置 │ │ ├── 13.network.yml # network-配置 │ │ ├── 20.db.yml # database-配置 │ │ ├── 21.storage.yml # storage-配置 │ │ ├── 22.lb.yml # lb-配置 │ │ ├── 23.node.yml # node-配置 │ │ └── 90.setup.yml # setup-配置 │ ├── thirdparty # 第三方服务对接 │ │ ├── addmaster.yml # 增加master-role │ │ ├── addnode.yml # 增加node-role │ │ └── setup.yaml # 配置安装 │ ├── tools # 工具包目录 │ │ ├── get_images.sh # 拉取docker镜像 │ │ ├── update-domain.sh # 更换域名 │ │ └── yc-ssh-key-copy.sh # 批量部署服务器ssh-key │ ├── upgrade # 升级配置目录 │ │ └── upgrade.yml # 升级配置文件 │ ├── vagrant # vagrant服务配置目录 │ │ ├── README.md # 说明文件 │ │ ├── Vagrantfile # ruby获取系统信息 │ │ ├── install.sh # 安装文件 │ │ └── setup.sh # 配置文件 │ └── windows # windows节点配置目录 │ ├── cni # 配置文件目录 │ ├── scripts # 脚本目录 │ │ ├── helper.psm1 # 帮助信息脚本 │ │ ├── hns.psm1 # hns配置脚本 │ │ ├── start-flannel. # 开启flannel脚本 │ │ ├── start-kubelet. # 开始kubelet脚本 │ │ └── start-node.ps1 # 开始node服务脚本 │ ├── README.md # 说明文件 │ ├── daemon.json # 域名配置 │ ├── net-conf.json # 网络配置 │ └── win.yaml # Windows配置 ├── inventory # Ansible剧本执行主机 │ ├── hosts.all # 主机模版 │ └── hosts.master # 主机模版 ├── log # 日志文件目录 ├── offline # 离线安装配置文件目录 │ ├── image # 离线包制作脚本目录 │ │ ├── download.sh # 缓存docker离线镜像脚本 │ │ ├── image.txt # rainbond镜像列表 │ │ ├── load.sh # 加载离线缓存镜像包脚本 │ │ └── offimage.sh # 压缩理想缓存镜像包脚本 │ └── pkgs # 离线包存储目录 │ ├── Dockerfile.centos # 构建离线CentOS镜像 │ ├── Makefile # 构建离线CentOS镜像 │ ├── README.md # 说明文档 │ ├── download.centos # 创建本地CentOS源 │ └── rbd.repo # Centos源 ├── scripts # 部署脚本存放目录 │ ├── installer # 安装脚本目录 │ │ ├── default.sh # 默认网络配置脚本 │ │ ├── functions.sh # 安装的示例库脚本 │ │ └── global.sh.example # 全局变量示例脚本 │ ├── op # 网络配置目录 │ │ ├── README.md # 说明文件 │ │ ├── lb.sh # 配置lb服务脚本 │ │ └── network.sh # 配置网络脚本 │ ├── upgrade # 升级脚本目录 │ │ └── upgrade.sh # 升级脚本文件 │ ├── yaml # 网络配置剧本目录 │ │ ├── init_network.yaml # 初始化网络剧本 │ │ └── reset_network.yaml # 重置网络剧本 │ └── node.sh # 用于管理节点脚本 ├── test # 测试剧本语法脚本目录 │ ├── hosts.ini # 主机配置信息 │ ├── k8s-master.role.1.j2 # k8s-master配置信息 │ ├── k8s-worker.role.1.j2 # k8s-worker配置信息 │ ├── kubelet.sh.1.j2 # kubelet配置信息 │ └── test.sh # 检测Ansible剧本语法脚本 ├── roles # Ansible部署规则配置文件目录 │ ├── bootstrap # bootstrap服务规则配置 │ ├── db # database服务规则配置 │ ├── docker # docker服务规则配置 │ ├── etcd # etcd服务规则配置 │ ├── k8s # k8s服务规则配置 │ ├── lb # lb服务规则配置 │ ├── monitor # monitor服务规则配置 │ ├── network_plugin # network_plugin服务规则配置 │ ├── node # node服务规则配置 │ ├── prepare # prepare服务规则配置 │ ├── rainvar # rainvar服务规则配置 │ ├── storage # storage服务规则配置 │ ├── thirdparty # thirdparty服务规则配置 │ └── upgrade # upgrade服务规则配置 ├── docs # 说明文档文件夹 ├── CHANGELOG.md # 版本迭代说明 ├── Dockerfile # 创建rainbond-ansible的Ubuntu镜像源 ├── LICENSE # 开发协议 ├── Makefile # 语法检测配置 ├── README.md # 说明文件 ├── addmaster.yml # 增加master节点剧本 ├── addnode.yml # 增加node节点剧本 ├── ansible.cfg # Ansible程序配置优化 ├── lb.yml # 增加lb节点剧本 ├── setup.sh # 主安装脚本入口 ├── setup.yml # Ansible本地安装剧本 ├── upgrade.yml # Ansible升级剧本 └── version # 安装包版本
2.2 ansible-playbook各角色剧本
角色 剧本 说明 manage rainvar 初始化私有数据中心的一些默认配置(数据库、端口、安装路径、安装版本等)
bootstrap 对本节点的内核进行优化(tcp_tw_recycle、core.somaxconn、syncookies、file-max等)
prepare 对本节点安装条件进行检查(系统版本、CPU、内存、磁盘、内核等)
storage/nfs/client 以nfs方式挂载本节点的存储卷
storage/nas 以nas方式挂载本节点的存储卷
storage/gfs 以gfs方式挂载本节点的存储卷
docker/install 在本节点上安装Docker服务
k8s/manage 在本节点上安装k8s服务的管理端
etcd/manage 在本节点上安装etcd服务的管理端
gateway 在本节点上安装负载均衡组件
monitor 在本节点上安装监控组件
network_plugin/calico 切换docker网络为calico
network_plugin/flannel 切换docker网络为flannel
node/exm 安装基础依赖包(python-pip、ansible)
node/core 在本节点安装node核心组件
gateway rainvar 初始化私有数据中心的一些默认配置(数据库、端口、安装路径、安装版本等)
bootstrap 对本节点的内核进行优化(tcp_tw_recycle、core.somaxconn、syncookies、file-max等)
prepare 对本节点安装条件进行检查(系统版本、CPU、内存、磁盘、内核等)
storage/nfs/client 以nfs方式挂载本节点的存储卷
storage/nas 以nas方式挂载本节点的存储卷
storage/gfs 以gfs方式挂载本节点的存储卷
docker/install 在本节点上安装Docker服务
network_plugin/calico 切换docker网络为calico
network_plugin/flannel 切换docker网络为flannel
gateway 在本节点上安装负载均衡组件
node/exlb 在本节点安装node负载组件
compute rainvar 初始化私有数据中心的一些默认配置(数据库、端口、安装路径、安装版本等)
bootstrap 对本节点的内核进行优化(tcp_tw_recycle、core.somaxconn、syncookies、file-max等)
prepare 对本节点安装条件进行检查(系统版本、CPU、内存、磁盘、内核等)
storage/nfs/client 以nfs方式挂载本节点的存储卷
storage/nas 以nas方式挂载本节点的存储卷
storage/gfs 以gfs方式挂载本节点的存储卷
docker/install 在本节点上安装Docker服务
k8s/compute 在本节点上安装k8s服务的客户端
etcd/compute 在本节点上安装etcd服务的客户端
network_plugin/calico 切换docker网络为calico
network_plugin/flannel 切换docker网络为flannel

gateway
node/core
在本节点上安装负载均衡组件
在本节点安装node核心组件
2.3 安装脚本部署流程
2.3.1 集群初始化
集群初始化包括三个重要步骤,安装脚本获取、安装环境构建和第一个节点的安装。 ./grctl init 各种参数 安装脚本获取
grctl init 命令从github仓库获取指定版本的ansible代码,如果离线安装没有此步骤。 安装环境构建
grctl init 命令根据用户指定的参数和默认值生成ansible global.sh 全局配置文件。 配置文件: /opt/rainbond/rainbond-ansible/scripts/installer/global.sh 主要配置: INSTALL_TYPE # 安装类型(离线/联网) DEPLOY_TYPE # 节点类型 DOMAIN # 域名 VERSION # 版本 STORAGE # 存储类型 STORAGE_ARGS # 挂载参数 NETWORK_TYPE # 网络类型 ROLE # 第一个节点角色(默认manage、gateway、compute)
这里的参数主要是指定Rainbond集群在存储、网络、安装模式等关键参数。 第一个节点安装
单一节点的安装根据传入role角色属性,传递属性给主安装脚本 setup.sh
主安装脚本在进行本地节点系统优化之后调用ansible-playbook使用 setup.yml 剧本进行第一个节点部署
剧本主要根据master主机组的role进行配置装机(系统优化、组件部署)
2.3.2 compute、gateway节点扩容安装 传入需要安装的role角色属性(compute,gateway),传递给主安装脚本 setup.sh 主安装脚本在进行远程节点系统优化之后调用ansible-playbook使用角色对应的剧本进行部署 manage 角色属性调用 addmaster.yml compute 角色属性调用 addnode.yml gateway 角色属性调用 gateway.yml 剧本主要根据主机组所使用的role进行配置装机(系统优化、组件部署)
3. 集群安装流程 graph LR subgraph 初始化过程 id1(grctl)==>id2(setup.sh) id2(setup.sh)==>id3(ansible-playbook) end
3.1 grctl init 初始化过程
grctl init 命令首先获取安装包,然后根据传入的参数以键值对的方式转换为shell脚本变量,以全局变量的方式对后续操作进行参数的传递,后续步骤读取全局变量,达到安装过程中对可变因素的掌控。 在未来的版本中,grctl命令行进一步控制ansible的主机列表,准确的为ansible提供集群主机序列。
3.2 shell 初始化过程
grctl 命令完成参数配置后调用安装脚本 /opt/rainbond/rainbond-ansible/setup.sh 进行第一个节点初始化。
脚本首先会对操作系统进行优化。这里是安装过程使用网络的主要点,在线安装模式下,操作系统的更新和配置,安装包的下载通过网络进行。离线安装模式下使用事先准备的本地安装源对操作系统进行基础环境安装,然后使用事先下载好的安装包。后续的节点安装过程将不再使用网络。
最后会调取ansible-play使用 setup.yml 剧本进行初始化安装。
3.3 ansible-playbook 初始化过程
ansible-playbook使用 setup.yml 进行初始化,首先会找到当前主机所在的主机组,之后根据role的设定到不同的组件文件夹中根据pre_task -> roles -> tasks -> post-tasks 的顺序依次执行文件夹下面的 main.yml 达到组件安装的作用
3.4 其他角色节点扩容安装 grctl node add --host <计算节点主机名> --iip <计算节点内网ip> --root-pass <计算节点root密码> --role gateway,compute 指定新增节点的 主机名、内网地址、连接密码、角色 , grctl命令行首先将节点数据加入集群元数据。通过 grctl node list 命令即可查询节点状态。 使用 grctl node install host-uuid 命令安装节点,grclt从API中读取相应的主机信息传递给 node.sh 脚本进行节点的安装。 node.sh 在 script/node.sh 中,主要获取以下几个参数: node_role # 新增节点的角色 node_hostname # 新增节点的主机名 node_ip # 新增节点的网络地址 login_type # 新增节点的登陆方式 login_key # 新增节点的连接密码 node_uuid # 新增节点的uuid node.sh 脚本首先会判断 node_role 中传递的角色属性,循环角色属性判断 inventory/hosts 中相应的主机组中是否存在对应的主机,没有根据不同的角色属性加入到相应的主机组中进行装机,在维护 inventory/hosts 之后会进行连接检测通过 login_type、login_key、node_uuid、node_ip、node_hostname 参数进行主机连接检测、通过之后会调用 ansible-playbook -i inventory/hosts -e $node_role role.yml 进行不同角色的装机: -i 指定装机主机 -e 将 grctl 传递给 setup.sh 的 node_role 参数传递给 ansible-playbook 生成对应的 node组件角色配置文件 role.yml 不同角色对应不同的yml配置文件 addmaster.yml # master role addnode.yml # compute role gateway.yml # gateway role 在5.1.6版本中hosts文件的维护将移交到grctl命令行工具中,根据集群节点状态实时生成。
4. 节点服务运维
Rainbond集群安装的所有组件有两种运行方式: node组件和docker组件是直接二进制运行,其他组件全部采用容器化运行。两种运行方式都是直接采用systemd守护进程进行守护。因此能够安装Rainbond的操作系统必须具有systemd。
在集群自动化运维的需求下,我们需要对节点(特别是计算节点)进行实时全面的健康检查,以确认节点是否可用。这个工作由node服务进行,它会根据 /opt/rainbond/conf 目录下配置对当前节点的配置检查项进行监控,如果出现故障汇报到集群管理端,如果是计算节点则会由集群管理端决策是否暂时禁止调度或下线该节点。 graph LR subgraph 服务运维流程 id1(systemd)==>id2(node.service) id2(node.service)==>id3(健康检测) id2(node.service)==>id4(守护进程) end
4.1 systemd的配置文件生成(node.service) 在集群初始化完成之后ansible会在 /etc/systemd/system/node.service 目录下生成 node.service 的配置文件, node 服务在 systemd 中以守护进程方式启动运行。 node服务启动后将读取 /opt/rainbond/conf 目录下的配置生成每一个需要启动服务的systemd配置文件并调用systemctl工具启动服务。
配置文件分为需求启动服务和只是健康检查项目,比如以下配置: - name: rbd-mq endpoints: - name: MQ_ENDPOINTS protocol: http port: 6301 health: name: rbd-mq model: http address: 127.0.0.1:6301/health max_errors_num: 3 time_interval: 5 after: - docker type: simple pre_start: docker rm rbd-mq start: >- docker run --name rbd-mq --network host -i goodrain.me/rbd-mq:V5.1-dev --log-level=debug --etcd-endpoints=${ETCD_ENDPOINTS} --hostIP=192.168.195.1 stop: docker stop rbd-mq restart_policy: always restart_sec: 10
该文件配置了rbd-mq服务的启动方式、健康检查方式和服务注册信息。
4.2 node组件的健康检测机制
每一个安装服务的健康检查配置见文档: 详细配置
若某项检查项目标识为不健康状态,当前节点将被标识为不健康状态。 对于不健康的节点Rainbond提供两级自动处理机制: 检测到异常的服务一段时间依然未恢复(取决于配置的时间段)将自动重启服务。 若计算节点被标注为不健康,节点控制器将会自动将其禁止应用调度直到节点恢复健康。 配置文件: /opt/rainbond/conf/health.yaml name # 需要检测的服务名称 model # 以什么方式检测(tcp/http/cmd) address # 被检测服务的地址 max_errors_num # 最大错误次数 time_interval # 每次检测次数 目前检测方式有3种 cmd # 使用脚本或者命令行 tcp # 使用ip:port模式 http # 使用http协议检测
4.3 集群故障查询和处理
根据整个集群节点的健康检查机制,用户在管理节点通过 grctl cluster 命令即可查询整个集群的故障点,或使用监控报警系统及时发现集群故障。当集群某个节点出现问题时首先定位故障的服务,并查看其运行日志处理故障。如果有未完善的健康检测项目,用户可以通过上诉节点健康检测配置方式自定义检测项目。
5. 常见安装问题解决思路 端口被占用无法安装 Rainbond是一个完整的PaaS平台解决方案,所以强烈建议使用干净的物理机或虚拟机安装Rainbond。 Rainbond网关节点直接承接应用访问流量,因此其默认占用80\443等关键端口。另外Rainbond安装Ansible默认使用的SSH端口是22,严格运维时需要设置。 数据库安装初始化失败 Rainbond 5.1.5及之前版本默认安装的mysql数据库版本是mariadb 10,其所需的内存资源是较大的。如果你的机器资源有限很大可能导致安装失败。后续的版本中我们将默认安装的数据库版本升级到Mysql 5.7系列。 高可用安装怎么做 本文主要描述了整个安装原理,因此你阅读本文应该可以总结出Rainbond高可用安装的关键,我们近期也会再次更新高可用安装操作指南。 Rainbond能否安装在Mac或Windows系统 Rainbond计算节点可以支持Windows操作系统来运行Windows应用,目前Windows的支持是企业版功能之一,因此开源安装脚本暂不支持Windows 节点的快速安装。Mac操作系统不适合安装Rainbond。开发者可以将部分组件运行在Mac下运行开发。 遇到其他安装问题怎么办? 移步 https://github.com/goodrain/rainbond-ansible/issues 查找或提交你的问题。
Rainbond项目官网
云计算
2019-08-19 15:11:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
2019年8月12日,Rainbond发布5.1.6版本,本次版本更新带来了更简化的Rainbond高可用安装方案,解决了社区用户反馈的BUG问题。 Rainbond:支撑企业应用开发、架构、交付和运维的全流程,通过“无侵入”架构无缝衔接各类企业应用,底层资源可以对接和管理IaaS、虚拟机、物理服务器。 发布版本:5.1.6 版本更新:推荐 更新范围:高可用安装
高可用安装
在本次版本更新中,为了是用户高可用安装Rainbond更加简单,我们对Rainbond安装脚本项目进行了如下调整: Ansible主机列表配置从脚本维护更改为安装工具从集群获取节点数据进行维护,使主机列表信息准确表达。 调整节点角色安装策略,支持灵活的角色组合安装和增量角色安装。 调整部分服务的部署配置参数,降低在资源有限环境下的部署失败率。 更改API证书签发逻辑,默认使用外网IP地址作为证书签发目标。 更改外部数据库的支持策略以支持阿里云RDS数据库。
基于安装脚本的调整,我们提供了两篇高可用安装文档供用户参考:
1. 基于阿里云高可用安装Rainbond平台
2. 私有云环境下高可用安装Rainbond平台
BUG修复 修复在批量多个服务构建时代码缓存目录冲突导致部分服务构建失败的问题 修复环境变量值存储最大限制256导致部分环境变量无法设置的问题,更改为最大限制1024 goodrain/rainbond#338 修复镜像创建服务时私有用户名密码长度限制过低导致无法设置账号密码问题 goodrain/rainbond#352 修复共享存储、本地存储无法修改挂载路径的问题 goodrain/rainbond#347 修复性能分析插件、入口网络治理插件同时开启时无法进行性能分析的问题 goodrain/rainbond#318 修复Pod状态错误时导致平台统计租户使用资源错误的问题 goodrain/rainbond#328 修复node日志收集模块获取容器元数据失败导致node奔溃的问题 goodrain/rainbond#331 修复镜像创建服务时识别限制内存值不为2的n次方,导致无法水平升级问题。 goodrain/rainbond-console#186 修复版本管理中构建失败的版本依然提供升级选项的问题 goodrain/rainbond-console#207 修复网关访问策略编辑时丢失https配置的问题 goodrain/rainbond-ui#174 Java类服务构建源设置,更改OracleJDK设置为自定义JDK设置,便于用户发散性使用此功能。 goodrain/rainbond-ui#169 修复应用管理页面的便捷添加组件中的从应用市场安装搜索问题和无法安装的问题 goodrain/rainbond-ui#166 修复依赖服务连接信息显示不全的问题 goodrain/rainbond-ui#171
版本升级
升级要求和注意事项 V5.1.6版本支持从V5.1.2 - V5.1.6版本升级,如果你还未升级到V5.1.2版本,参考 V5.1.x版本升级文档 ,先升级至V5.1.2版本:
grctl version , 例如5.1.5版本显示如下: Rainbond grctl v5.1.5-release-381a1da-2019-07-12-15 升级过程会重启管理服务,因此只有单管理节点的集群会短暂影响控制台操作,请选择合理的升级时间段 。 升级过程脚本需要从集群获取节点数据,请务必在集群正常工作情况下进行升级。
下载 5.1.6 更新包 离线包镜像大小约1.3GB,需要保证当前集群磁盘可用空间至少不低于2G # Rainbond 组件升级包 wget https://pkg.rainbond.com/offline/5.1/rainbond.images.2019-08-11-5.1.6.tgz -O /grdata/services/offline/rainbond.images.upgrade.5.1.6.tgz # 升级脚本包 wget https://pkg.rainbond.com/offline/5.1/rainbond-ansible.upgrade.5.1.6.tgz -O /grdata/services/offline/rainbond-ansible.upgrade.5.1.6.tgz
第一个管理节点执行下述命令升级平台 rm -rf /tmp/rainbond-ansible rm -rf /grdata/services/offline/upgrade mkdir -p /tmp/rainbond-ansible tar xf /grdata/services/offline/rainbond-ansible.upgrade.5.1.6.tgz -C /tmp/rainbond-ansible cd /tmp/rainbond-ansible/scripts/upgrade/ bash ./upgrade.sh
平台升级完成验证 执行 grctl cluster 确定所有服务和节点运行正常 grctl version 确认版本已升级到5.1.6,运行组件镜像版本为 v5.1.6-release
插件升级
本次版本更新了性能分析插件,请在平台完成升级后按照如下方式升级插件: 管理节点执行以下命令: docker pull rainbond/plugins-tcm:5.1.6 docker tag rainbond/plugins-tcm:5.1.6 goodrain.me/tcm docker push goodrain.me/tcm 进入平台,不同的团队分别进入插件管理,选择性能分析插件,点击插件的构建。 更新使用当前插件的服务,插件即可生效。
福利来了:
每周三晚上8:30,开源Rainbond产品【线上培训】调整为开源Rainbond用户【线上沙龙】,内容变得更丰富。在每一期的精心选题的基础上,又增加了开源爱好者的互动环节。
【线上交流沙龙 第十期预告】Rainbond高可用集群安装上手、新版本解读。
查看往期: https://www.rainbond.com/video.html
【第一期】Jenkins Pipeline 对接Rainbond用法培训
【第二期】SpringCloud微服务架构应用部署到Rainbond用法培训
【第三期】Eurka集群部署,SpringCloud微服务架构升级培训
【第四期】Java生态的微服务无侵入链路追踪
【第五期】Rainbond 应用市场解读
【第六期】滚动、蓝绿、灰度如何在Rainbond实现
【第七期】服务熔断和全局限流如何在Rainbond实现
【第八期】Rainbond日志管理与插件制作教程
【第九期】Rainbond安装与运维原理解读
参与方式:添加微信进群⬇️
云计算
2019-08-13 10:26:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
Pod是K8S调度的基本单位,一个Pod中包含了一个Pause容器(根容器)和多个用户容器。Pause容器的状态代表了Pod的状态,同一个Pod中的容器共享相同的ip和数据卷。
因此,Pod的创建首先要构建一个Sandbox,包含pause容器并初始化网络/数据卷等一系列资源。
1.sandbox构建
创建一个Pod首先必须要构建一个Sandbox,创建流程如下:
createPodSandbox是创建的入口方法,包含两个步骤:
generatePodSandboxConfig - 构造sandbox的config,当中涉及到通过Kubelet生成sandbox的cgroupParent
RunPodSandBox - 通过Docker Service(dockershim)来创建sandbox的container并启动。包含四个主要步骤:1. pull image; 2. create container; 3. start container; 4. init network
经过以上两个步骤,pod所需的sandbox就运行起来了,同时初始化好了网络/数据卷等资源。
2.用户Container创建
建立了运行的SandBox之后,就可以创建用户Container,并加入到Pod中。主要流程如下:
主要包含三个步骤:
EnsureImageExists - 拉取镜像
CreateContainer - 通过CRI & dockershim创建容器实例
StartContainer - 运行实例
云计算
2019-07-31 17:28:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
本文作者:yunzhixueyuan
百度大脑开放日·AI战疫专场
直播时间:2020年3月13日15:00-16:00
百度大脑自2016年启动开放以来,已打造成为业内最全面、最领先的AI开放平台,服务规模、调用量都居于业界第一。
百度大脑开放日于2019年开办,覆盖北/上/深等地与众多AI开发者、合作伙伴近距离沟通及深度交流,一起分享百度大脑最新产品和应用案例。
2020我们再度起航,结合当下疫情,推出了百度大脑开放日“AI战疫”专场,将于3月13日通过直播形式与大家见面,全面解析百度大脑在疫情期间助力“抗疫”所开放的能力及应用案例,同时也将分享更多百度大脑新近技术能力。
敬请期待!
直播议程:
一、百度大脑“AI开发者战疫守护计划”详解
二、百度大脑“AI战疫”应用案例解析
1、口罩检测及红外测温方案助力公共卫生防控
2、 飞桨 开源助力智能诊疗
3、OCR赋能社区防疫与高效信息采集
4、语音技术让在线教与学更轻松
三、百度大脑新近开放能力介绍
四、Q&A
直播嘉宾:双双 百度AI技术生态部高级产品经理
原文链接地址: https://developer.baidu.com/topic/show/290680
云计算
2020-03-10 13:54:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
MicroK8s与K3s都是基于Kubernetes的轻量级发行版,主要面向工作站、边缘计算、物联网等应用场景,但二者也有比较大的区别。
主要区别 MicroK8s主要将一些扩展件集成到系统中,而K3s却将很多扩展件独立出来。 虽然MicroK8s与K3s都能支持ARM体系的低功耗计算,但是MicroK8s主打使用方便性,也更适合开发团队使用,而K3s主打轻量化,更适合低功耗的小型化无人值守的自动化系统使用。 MicroK8s的集群管理内核与Kubernetes标准版的容器镜像是完全一样的,而K3s的内核进行了一些修改,部分模块可能由于兼容性问题无法运行。
K3s修改的部分
主要包括:
删除 过时的功能和非默认功能 Alpha功能 内置的云提供商插件 内置的存储驱动 Docker (可选)
新增 简化安装 除etcd外,还支持SQLite3数据存储 TLS管理 自动的Manifest和Helm Chart管理 containerd, CoreDNS, Flannel
MicroK8s主要的变化
主要包括: 基于snap的安装工具。 kubectl的命名空间化,变为microk8s.kubectl。 各种扩展模块的版本适配,本地存储的直接支持。 各种addon动态加载模块,支持快速Enable/Disable。 内置的GPU支持。
更多参考 K3s官方文档 k3s/k3OS-轻量级Kubernetes及操作系统 MicroK8s 快速入门 Ubuntu发布Microk8s及其GPU支持 MicroK8s-部署到Windows、macOS和Raspberry Pi KubeFlow-在Microk8s部署与应用
云计算
2020-03-04 15:09:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
本文作者:oschina_2020
移动开发领域近年来已经逐渐告别了野蛮生长的时期,进入了相对成熟的时代。而一直以来 Native 和 Web 的争论从未停止,通过开发者孜孜不倦的努力,Web 的效率和 Native 的体验也一直在寻求着平衡。本文聚焦 iOS 开发和 Web 开发的交叉点,内容涉及到 iOS 开发中全部的 Web 知识,涵盖从基础使用到 WebKit、从 JSCore 到大前端、从 Web 优化到业务扩展等方面,希望通过简要的介绍,帮助开发者一窥 Hybrid 和大前端的构想。
iOS 中 Web 容器与加载
1. iOS 中的 Web 容器
目前 iOS 系统为开发者提供三种方式来展示 Web 内容,分别是 UIWebView、WKWebView 和 SFSafariViewController:
UIWebView
UIWebView 从 iOS2 开始就作为 App 内展示 Web 内容的容器,但是长久以来一直遭受开发者的诟病,它存在系统级的内存泄露、极高内存峰值、较差的稳定性、Touch Delay 以及 JavaScript 的运行性能和通信限制等问题。在 iOS12 以后已经被标记为 Deprecated 不再维护。 WKWebView
在 iOS8 中,Apple 引入了新一代的 WebKit framework,同时提供了 WKWebView 用来替代传统的 UIWebView,它更加稳定,拥有 60fps 滚动刷新率、丰富的手势、KVO、高效的 Web 和 Native 通信,默认进度条等功能,而最重要的是,它使用了和 Safari 相同的 Nitro 引擎极大提升了 JavaScript 的运行速度。WKWebView 独立的进程管理,也降低了内存占用及 Crash 对主 App 的影响。 SFSafariViewController
在 iOS9 中,Apple 引入了 SFSafariViewController,其特点就是在 App 内可以打开一个高度标准化的、和 Safari 一样界面和特性的页面。同时 SFSafariViewController 支持和 Safari 共享 Cookie 和表单数据。
这几中容器如何选择呢?
对于 SFSafariViewController,由于其标准化程度之高,使之界面和交互逻辑无法和 App 统一,基于 App 整体体验的考虑,一般都使用在相对独立的功能和模块中,最常见的就是在 App 内打开 App Store 或者广告、游戏推广的页面。
对于 UIWebView/WKWebView,如果说之前由于 NSURLProtocol 的问题,好多 App 都在继续使用 UIWebView,那么随着 App 放弃维护 UIWebView(iOS12),全部的 App 应该会陆续地切换到 WKWebView 中来。当然,最初 WKWebView 也为开发者们带来了一些难题,但是随着系统的升级与业务逻辑的适配也逐步得到修复,后文会列举几个最为关注的技术点。
UIWebView/WKWebView 对主 App 内存的影响:
2. WebKit 框架与使用
WebKit.framework
WebKit 是一个开源的 Web 浏览器引擎,每当谈到 WebKit,开发者常常迷惑于它和 WebKit2、Safari、iOS 中的框架,以及 Chromium 等浏览器的关系。
广义的 WebKit 其实就是指 WebCore,它主要包含了 HTML 和 CSS 的解析、布局和定位这类渲染 HTML 的功能逻辑。而狭义的 WebKit 就是在 WebCore 的基础上,不同平台封装 JavaScript 引擎、网络层、GPU 相关的技术(WebGL、视频)、绘制渲染技术以及各个平台对应的接口,形成我们可以用的 WebView 或浏览器,也就是所谓的 Webkit Ports。
比如在 Safari 中 JS 的引擎使用 JavascriptCore,而 Chromium 中使用 v8;渲染方而 Safari 使用 CoreGraphics,而 Chromium 中使用 skia;网络方而 Safari 使用 CFNetwork,而 Chromium 中使用 Chromium stack 等等。而 Webkit2 是相对于狭义上的 Webkit 架构而言,主要变化是在 API 层支持多进程,分离了 UI 和 Web 接口的进程,使之通过 IPC 来进行通讯。
iOS 中的 WebKit.framework 就是在 WebCore、底层桥接、JSCore 引擎等核心模块的基础上,针对 iOS 平台的项目封装,它基于新的 WKWebView,提供了一系列浏览特性的设置,以及简单方便的加载回调。
Web 容器使用流程与关键节点
对于大部分日常使用来说,开发者需要关注的就是 WKWebView 的创建、配置、加载、以及系统回调的接收。
对于 Web 开发者,业务逻辑一般通过基于 Web 页面中 Dom 渲染的关键节点来处理,而对于 iOS 开发者,WKWebView 提供的的注册、加载和回调时机,没有明确地与 Web 加载的关键节点相关联。准确地理解和处理两个维度的加载顺序,选择合理的业务逻辑处理时机,才可以实现准确而高效的应用。
WKWebView 常见问题
使用 WKWebView 带来的另外一个好处,就是我们可以通过源码理解部分加载逻辑,为 Crash 提供一些思路,或者使用一些私有方法处理复杂业务逻辑。 NSURLProtocol
WKWebView 最为显著的改变,就是不支持 NSURLProtocol,为了兼容旧的业务逻辑,一部分 App 通过 WKBrowsingContextController 中的非公开方法实现了 NSURLProtocol。 // WKBrowsingContextController + (void)registerSchemeForCustomProtocol:(NSString *)scheme WK_API_DEPRECATED_WITH_REPLACEMENT("WKURLSchemeHandler", macos(10.10, WK_MAC_TBA), ios(8.0, WK_IOS_TBA));
在 iOS11 中,系统增加了 setURLSchemeHandler 函数用来拦截自定义的 Scheme,但是不同于 UIWebView,新的函数只能拦截自定义的 Scheme(SchemeRegistry.cpp),对使用最多的 HTTP/HTTPS 依然不能有效地拦截。 //SchemeRegistry static const StringVectorFunction functions[] { builtinSecureSchemes, // about;data... builtinSchemesWithUniqueOrigins, // javascript... builtinEmptyDocumentSchemes, builtinCanDisplayOnlyIfCanRequestSchemes, builtinCORSEnabledSchemes, //http;https }; 白屏
通常 WKWebView 白屏的原因主要分两种,一种是由于 Web 的进程 Crash(多见于内部进程通信);一种就是 WebView 渲染时的错误(Debug 一切正常只是没有对应的内容)。对于白屏的检测,前者在 iOS9 之后系统提供了对应 Crash 的回调函数,同时业界也有通过判断 URL/Title 是否为空的方式作为辅助;后者业界通过视图树对比,判断 SubView 是否包含 WKCompsitingView,以及通过随机点截图等方式作为白屏判断的依据。 其它 WKWebView 的系统级问题 如 Cookie、POST 参数、异步 JavaScript 等,可以通过业务逻辑的调整重新适配。 由于 WebKit 源码的开放性 ,我们也可以利用私有方法来简化代码逻辑、实现复杂的产品需求。例如在 WKWebViewPrivate 中可以获得各种页面信息、直接取到 UserAgent、 在 WKBackForwardListPrivate 中可以清理掉全部的跳转历史、以及在 WKContentViewInteraction 中替换方法实现自定义的 MenuItem 等。 @interface WKWebView (WKPrivate) @property (nonatomic, readonly) NSString *_userAgent WK_API_AVAILABLE(macosx(10.11), ios(9.0)); ... @interface WKBackForwardList (WKPrivate) - (void)_removeAllItems; ... @interface WKContentView (WKInteraction) - (BOOL)canPerformActionForWebView:(SEL)action withSender:(id)sender;
3. App 中的应用场景
WKWebView 系统提供了四个用于加载渲染 Web 的函数,这四个函数从加载的类型上可以分为两类:加载 URL & 加载 HTML\Data。所以基于此也延伸出两种不同的业务场景:加载 URL 的页面直出类和加载数据的模板渲染类,同时两种类型各自也有不同的优化重点及方向。
页面直出类 //根据URL直接展示Web页面 - (nullable WKNavigation *)loadRequest:(NSURLRequest *)request;
通常各类 App 中的 Web 页面加载都是通过加载 URL 的方式,比如嵌入的运营活动页面、广告页面等等。
模板渲染类 //根据模板&数据渲染Web页面 - (nullable WKNavigation *)loadHTMLString:(NSString *)string baseURL:(nullable NSURL *)baseURL; ...
需要使用 WebView 展示,且交互逻辑较多的页面,最常见的就是资讯类 App 的内容展示页。
iOS 中 Web 与 Native 的通信
单纯使用 Web 容器加载页面已经不能满足复杂的功能,开发者希望数据可以在 Native 和 Web 之间通信传递来实现复杂的功能,而 JavaScript 就是通信的媒介。对于有 WebView 的情况,虽然 WKWebView 提供了系统级的方法,但是大部分 App 仍然使用基于 URLScheme 的 WebViewBridge 用以兼容 UIWebView。而脱离了 WebView 容器,系统提供了 JavascriptCore 的框架,它也为之后蓬勃发展的跨平台和热修复技术提供了可能。
1. 基于 WebView 的通信
基于 WebView 的通信主要有两个途径,一个是通过系统或私有方法,获取 WebView 当中的 JSContext,使用系统封装的基于 JSCore 的函数通信;另一类是通过创建自定义 Scheme 的 iframe Dom,客户端在回调中进行拦截实现。
UIWebView & WKWebView 系统级
在 UIWebView 时代没有提供系统级的函数进行 Web 与 Native 的交互,绝大部分 App 都是通过 WebViewJavascriptBridge(下节介绍)来进行通信,而由于 JavascriptCore 的存在,对于 UIWebView 来说只要有效的获取到内部的 JSContext,也可以达到目的。目前已知的有效获取 Context 的私有方法如下: //通过系统废弃函数获取context - (void)webView:(WebView *)webView didCreateJavaScriptContext:(JSContext *)context forFrame:(WebFrame *)frame; //通过valueForKeyPath获取context self.jsContext = [_webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
在 WKWebView 中提供了系统级的 Web 和 Native 通讯机制,通过 Message Handler 的封装使开发效率有了很大的提升。同时系统封装了 JavaScript 对象和 Objective-C 对象的转换逻辑,也进一步降低了使用的门槛。 // js端发送消息 window.webkit.messageHandlers.{NAME}.postMessage() //Native在回调中接收 - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message; 拦截自定义 Scheme 请求 - WebViewJavascriptBridge
由于私有方法的稳定性与审核风险,开发者不愿意使用上文提到的 UIWebView 获取 JSContext 的方式进行通信,所以通常都采用基于 iframe 和自定义 Scheme 的 JavascriptBridge 进行通信。虽然在之后的 WKWebView 提供了系统函数,但是大部分 App 都需要兼容 UIWebView 与 WKWebView,所以目前的使用范围仍然十分广泛。
类似的开源框架有很多,但是无外乎都是 Web 侧根据固定的格式创建包含通信信息的 Request,之后创建隐式 iframe 节点请求;Native 侧在相应的 WebView 回调中解析 Request 的 Scheme,之后按照格式解析数据并处理。
而对于数据传递和回调处理的问题,在兼容两种 WebView、持续更新的 WebViewJavascriptBridge 中,iframe Request 没有直接传递数据,而是 Web 和 Native 侧维护共同的参数或回调 Queue,Native 通过 Request 中 Scheme 的解析触发对 Queue 里数据的读取。
2. 脱离 WebView 的通信 JavaScriptCore
JavascriptCore
JavascriptCore 一直作为 WebKit 中内置的 JS 引擎使用,在 iOS7 之后,Apple 对原有的 C/C++ 代码进行了 OC 封装,成为系统级的框架供开发者使用。作为一个引擎来讲,JavascriptCore 的词法、语法分析,以及多层次的 JIT 编译技术都是值得深入挖掘和学习的方向,由于篇幅的限制暂且不做深入的讨论。
JavascriptCore.framework
虽然 JavascriptCore.framework 只暴露了较少的头文件和系统函数,但却提供了在 App 中脱离 WebView 执行 JavaScript 的环境和能力。 JSVirtualMachine:提供了 JS 执行的底层资源及内存。虽然 Java 与 JavaScript 没有一点关系,但是同样作为虚拟机,JSVM 和 JVM 做了一部分类似的事情,每个 JSVirtualMachine 独占线程,拥有独立的空间和管理,但是可以包含多个 JSContext。 JSContext:提供了 JS 运行的上下文环境和接口,可以不准确地理解为,就是创建了一个 JavaScript 中的 Window 对象。 JSValue:提供了 OC 和 JS 间数据类型的封装和转换 Type Conversions。除了基本的数据类型,需要注意 OC 中的 Block 转换为 JS 中的 function、Class 转换为 Constructor 等等。 JSManagedValue:JavaScript 使用 GC 机制管理内存,而 OC 采用引用计数的方式管理内存。所以在 JavascriptCore 使用过程中,难免会遇到循环引用以及提前释放的问题。JSManagedValue 解决了在两种环境中的内存管理问题。 JSExport:提供了类、属性和实例方法的调用接口。内部实现是在 ProtoType & Constructor 中实现对应的属性和方法。
使用 JavascriptCore 进行通信
对于 JavascriptCore 粗浅的理解,可以认为使用 Block 方法,内部是将 Block 保存到一个 Web 环境中的全局 Object 中,例如 Window,而使用 JSExport 方法,则是在 Web 环境中 Object 的 prototype 中创建属性、实例方法,在 constructor 对象中创建类方法,从而实现 Web 中的调用。 Native - Web:通过 JavascriptCore,Native 可以直接在 Context 中执行 JS 语句,和 Web 侧进行通信和交互。 JSValue *value = [self.jsContext evaluateScript:@"document.cookie"]; Web - Native:对于 Web 侧向 Native 的通信,JavascriptCore 提供两种方式,注册 Block & Export 协议。 //Native self.jsContext[@"addMethod"] = ^ NSInteger(NSInteger a, NSInteger b) { return a + b; }; //JS console.log(addMethod(1, 2)); //3 //Native @protocol testJSExportProtocol @property (readonly) NSString *string; ... @interface OCClass : NSObject //JS var OCClass = new OCClass(); console.log(OCClass.string);
3. App 中的应用场景 基于 WebView 的通信,主要用于 App 向 H5 页面中注入的 JavaScript Open Api,如提供 Native 的拍照、音视频、定位,以及 App 内的登录与分享等功能。 JavascriptCore,则催生了动态化、跨平台以及热修复等一系列技术的蓬勃发展。
跨平台与热修复
近几年来国内外移动端各种跨平台方案如雨后春笋般涌现,“Write once, run anywhere”不再是空话。这些跨平台技术方案的切入点是在 Web 侧 DSL、virtualDom 等方面的优化,以及 Native 侧 Runtime 的应用与封装,但两端通信的核心,依然是 JavascriptCore。
除了对跨平台技术的积极探索,国内开发者对热修复技术也产生了极大的热情,同样作为 Native 和 Web 的交叉点,JavascriptCore 依然承担着整个技术结构中的通信任务。
1. 基于 Web 的热修复技术
对于国内的 iOS 开发者来说,审核周期、敏感业务、支付分成以及 bug 修复都催生了热修复方向的不断探索。在苹果加强审核之前,几乎所有大型的 App 都把热修复当成了 iOS 开发的基础能力,最近在《移动开发还有救么》一文中也详细地介绍了相关黑科技的前世今生。在所有 iOS 热修复的方案中,基于 JavaScript、同时也是影响最大的就是 JSPatch。
基于上文的分析,对于脱离 WebView 的 Native 和 Web 间的通信,我们只能使用 JavascriptCore。而在 JavascriptCore 中提供了两种方式用于通信,即 Context 注册 Block 的回调,以及 JSExport。对于热修复的场景来说,我们不可能把潜在需要修复的函数都一一使用协议进行注册,更不能对新增方法和删除方法等进行处理,所以在 Native 和 Web 通信这个维度,我们只能采用 Context 注册 Block 的方式。 // 注册回调 context[@"_OC_callI"] = ^id(JSValue *obj, NSString *selectorName, JSValue *arguments, BOOL isSuper) { return callSelector(nil, selectorName, arguments, obj, isSuper); }; context[@"_OC_callC"] = ^id(NSString *className, NSString *selectorName, JSValue *arguments) { return callSelector(className, selectorName, arguments, nil, NO); };

确定了通信采用 Block 回调的方式后,热修复就面临着如何在 JS 中调用类以及类的方法的问题。由于没有使用 JSExport 等方式,JS 是无法找到相应类等属性和方法的,在 JSPatch 中,通过简单的字符串替换,将所有方法都替换成通用函数 (__c),然后就可以将相关信息传递给 Native,进而使用 runtime 接口调用方法。 // 替换全部方法调用 static NSString *_replaceStr = @".__c(\"$1\")("; // 调用方法 __c: function(methodName) { ... return function(){ ... var ret = instance ? _OC_callI(instance, selectorName, args, isSuper): _OC_callC(clsName, selectorName, args) return _formatOCToJS(ret) }
当然对于 JSPatch 以及其它热修复的项目来说,Web 和 Native 通信只是整个框架中的一个技术点,更多的实现原理和细节由于篇幅的关系暂且不作介绍。
2. 基于 Web 的跨平台技术
随着 Google 开源了基于 Dart 语言的 Flutter,跨平台的技术又进入了一个新的发展阶段。对于传统的跨平台技术来讲,各个公司以 JavascriptCore 作为通信桥梁,围绕着 DSL 的解析、方法表的注册、模块注册通信、参数传递的设计以及 OC Runtime 的运用等不同方向,封装成了一个又一个跨平台的项目。
而在其中,以 JavaScript 作为前端 DSL 的跨平台技术方案里,Facebook 的 react-native 以及阿里(目前托管给了 Apache 软件基金会)的 Weex 最为流行。在网络上两者的比较文章有很多,集中在学习成本、框架生态、代码侵入、性能以及包大小等方面,各个业务可以根据自己的重点选择合理的技术结构。
而不管是 react-native 还是 Weex,Web 和 Native 的通信桥梁仍然是 JavascriptCore。 //weex 举例 JSValue* (^callNativeBlock)(JSValue *, JSValue *, JSValue *) = ^JSValue*(JSValue *instance, JSValue *tasks, JSValue *callback){ ... return [JSValue valueWithInt32:(int32_t)callNative(instanceId, tasksArray, callbackId) inContext:[JSContext currentContext]]; }; _jsContext[@"callNative"] = callNativeBlock;
和热修复技术一样,跨平台又是一个庞大的技术体系,JavascriptCore 仅仅是作为整个体系运转中的一个小小的部分,而整个跨平台的技术方案就需要另开多个篇幅进行介绍了。
iOS 中 Web 相关优化策略
随着 Web 技术的不断升级以及 App 动态性业务需求的增多,越来越多的 Web 页面加入到了 iOS App 当中,与之对应的,首屏展示速度体验这个至关重要的领域,也成为了移动客户端中 Web 业务最重要的优化方向。
1. 不同业务场景的优化策略
对于单纯的 Web 页面来说,业界早已有了合理的优化方向以及成熟的优化方案,而对于移动客户端中的 Web 来说,开发者在进行单一的 Web 优化时,还可以通过优化 Web 容器以及 Web 页面中数据加载方式等多个途径做出优化。
所以对于 iOS 开发中的优化来说,就是通过 Native 和 Web 两个维度的优化关键渲染路径,保证 WebView 优先渲染完毕。由此我们梳理了常规 Web 页面整体的加载顺序,从中找出关键渲染路径,继而逐个分析、优化。
2. Web 维度的优化
通用 Web 优化
对于 Web 的通用优化方案,一般来说在网络层面,可以通过 DNS 和 CDN 技术减少网络延迟、通过各种 HTTP 缓存技术减少网络请求次数、通过资源压缩和合并减少请求内容等。在渲染层面可以通过精简和优化业务代码、按需加载、防止阻塞、调整加载顺序优化等等。对于这个老生常谈的问题,业内已经有十分成熟和完整的总结,比如可以参考《Best Practices for Speeding Up Your Web Site》。
其它
脱离较为通用的优化,在对代码侵入宽容度较高的场景中,开发者对 Web 优化有着更为激进的做法。例如在 VasSonic 中,除了 Web 容器复用、数据模板分离、预拉取和通用的优化方式外,还通过自定义 VasSonic 标签将 HTML 页面进行划分,分段进行缓存控制,以达到更高的优化效果。
3. Native 维度的优化
容器复用和预热
WKWebView 虽然 JIT 大幅优化了 JS 的执行速度,但是单纯的加载渲染 HTML,WKWebView 比 UIWebView 慢了很多。根据渲染的不同阶段分别对耗时进行测试,同时对比 UIWebView,我们发现 WKWebView 在初始化及渲染开始前的耗时较多。
针对这种情况,业界主流的做法就是复用 & 预热。预热就是在 App 启动时创建一个 WKWebView,使其内部部分逻辑预热以提升加载速度。而复用又分为两种,较为复杂的是处理边界条件以达到真正的复用,还有一种较为取巧的办法就是常驻一个空 WKWebView 在内存。
HybridPageKit 提供了易于集成的完整 WKWebView 重用机制实现,开发者可以无需关注复用细节,无缝地体验更为高效的 WKWebView。
Native 并行资源请求 & 离线包
由于 Web 页面内请求流程不可控以及网络环境的影响,对于 Web 的加载来说,网络请求一直是优化的重点。开发者较为常用的做法是使用 Native 并行代理数据请求,替代 Web 内核的资源加载。在客户端初始化页面的同时,并行开始网络请求数据;当 Web 页面渲染时向 Native 获取其代理请求的数据。
而将并行加载和预加载做到极致的优化,就是离线包的使用。将常用的需要下载资源(HTML 模板、JS 文件、CSS 文件与占位图片)打包,App 选择合适的时机全部下载到本地,当 Web 页面渲染时向 Native 获取其数据。
通过离线包的使用,Web 页面可以并行(提前)加载页面资源,同时摆脱了网络的影响,提高了页面的加载速度和成功率。当然离线包作为资源动态更新的一个方式,合理的下载时机、增量更新、加密和校验等方面都是需要进行设计和思考的方向,后文会简单介绍。
复杂 Dom 节点 Native 化实现
当并行请求资源,客户端代理数据请求的技术方案逐渐成熟时,由于 WKWebView 的限制,开发者不得不面对业务调整和适配。其中保留原有代理逻辑、采用 LocalServer 的方式最为普遍。但是由于 WKWebView 的进程间通信、LocalServer Socket 建立与连接、资源的重复编解码都影响了代理请求的效率。
所以对于一些资讯类 App,通常采用 Dom 节点占位、Native 渲染实现的方式进行优化,如图片、地图、音视频等模块。这样不但能减少通信和请求的建立、提供更加友好的交互、也能并行地进行 View 的渲染和处理,同时减少 Web 页面的业务逻辑。
HybridPageKit 中就提供封装好的功能框架,开发者可以简单的替换 Dom 节点为 NativeView。
按优先级划分业务逻辑
从 App 的维度上看,一个 Web 页面从入口点击到渲染完成,或多或少都会有 Native 的业务逻辑并行执行。所以这个角度的优化关键渲染路径,就是优先保证 WebView 以及其它在首屏直接展示的 Native 模块优先渲染,所以承载 Web 页面的 Native 容器,可以根据业务逻辑的优先级,在保证 WebView 模块展示之后,选择合适的时机进行数据加载、视图渲染等。这样就能保证在 Native 的维度上,关键路径优先渲染。
4. 优化整体流程
整体上对于客户端来说,我们可以从 Native 维度(容器和数据加载)以及 Web 维度两个方向提升加载速度,按照页面的加载流程,整体的优化方向如下:
iOS 中 Web 相关延伸业务
1. 模板引擎
为了并行加载数据以及并行处理复杂的展示逻辑,对于非直出类型的 Web 页面,绝大部分 App 都采用数据和模板分离下发的方式。而这样的技术架构,导致在客户端内需要增加替换对应 DSL 的模板标签,形成最终的 HTML 业务逻辑。简单的字符串替换逻辑不但低效,还无法做到合理的组件化管理,以及组件合理地与 Native 交互,而模板引擎相关技术会使这种逻辑和表现分离的业务场景实现得更加简洁和优雅。
基于模板引擎与数据分离,客户端可以根据数据并行创建子业务模块,同时在子业务模块中处理和 Native 交互的部分,如图片裁剪适配、点击跳转等,生成 HTML 代码片段,之后基于模板进行替换生成完整的页面。这样不但减少了大量的字符串替换逻辑,同时业务也得到了合理拆分。
模板引擎的本质就是字符串的解析和替换拼接,在 Web 端不同的使用场景有很多不同语法的引擎类型,而在客户端较为流行的,有使用较为复杂的 MGTemplateEngine,它类似于 Smarty,支持部分模板逻辑。也有基于 mustache,Logic-less 的 GRMustache 可供选择。
2. 资源动态更新和管理
无论是离线包、本地注入的 JS、CSS 文件,还是本地化 Web 中的默认图片,目的都是通过提前下载,替换网络请求为本地读取来优化 Web 的加载体验和成功率,而对于这些资源的管理,开发者需要从下载与更新,以及 Web 中的访问这两个方面进行设计优化。
下载与更新 下载与重试:对于资源或是离线包的下载,选择合适的时机、失败重载时机、失败重载次数都要根据业务灵活调整。通常为了增加成功率和及时更新,在冷启动、前后台切换、关键的操作节点,或者采用定时轮循的方式,都需要进行资源版本号或 MD5 的判断,用以触发下载逻辑。当然对于服务端来说,合理的灰度控制,也是保证业务稳定的重要途径。 签名校验:对于动态下载的资源,我们都需要将原文件的签名进行校验,防止在传输过程中被篡改。对于单项加密的办法就是双端对数据进行 MD5 的加密,之后客户端校验 MD5 是否符合预期;而双向加密可以采用 DES 等加密算法,客户端使用公钥对资源验证使用。 增量更新:为了减少资源和离线包的重复下载,业内大部分使用离线包的场景都采用了增量更新的方式。即客户端在触发请求资源时,带上本地已存在资源的标示,服务端根据标示和最新资源做对比,之后只提供新增或修改的 Patch 供客户端下载。
基于 LocalServer 的访问
在完成资源的下载与更新后,如何将 Web 请求重定向到本地,大部分 App 都依赖于 NSURLProtocol。上文提到在 WKWebView 中虽然可以使用私有函数实现(或者 iOS11+ 提供的系统函数),但是仍然有许多问题。
目前业界一部分 App,都采用了集成 LocalServer 的方式,接管部分 Web 请求,从而达到访问本地资源的目的。同时集成了 LocalServer,通过将本地资源封装成 Response,利用 HTTP 的缓存技术,进一步的优化了读取的时间和性能,实现层次化的缓存结构。而使用了本地资源的 HTTP 缓存,就需要考虑缓存的控制和过期时间,通常可以通过在 URL 上增加本地文件的修改时间、或本地文件的 MD5 来确保缓存的有效性。
GCDWebServer 浅析
排除 Socket 类型,业界流行的 Objc 版针对 HTTP 开源的 WebServer,不外乎年久失修的 CocoaHTTPServer 以及 GCDWebServer。GCDWebServer 是一个基于 GCD 的轻量级服务器,拥有简单的四个模块:Server/Connection/Request/Reponse,它通过维护 LIFO 的 Handler 队列传入业务逻辑生成响应。在排除了基于 RFC 的 Request/Response 协议设计之后,关键的代码和流程如下: //GCDWebServer 端口绑定 bind(listeningSocket, address, length) listen(listeningSocket, (int)maxPendingConnections) //GCDWebServer 绑定Socket端口并接收数据源 dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, listeningSocket, 0, dispatch_get_global_queue(_dispatchQueuePriority, 0)); //GCDWebServer 接收数据并创建Connection dispatch_source_set_event_handler(source, ^{ ... GCDWebServerConnection* connection = [(GCDWebServerConnection*)[self->_connectionClass alloc] initWithServer:self localAddress:localAddress remoteAddress:remoteAddress socket:socket]; //GCDWebServerConnection 读取数据 dispatch_read(_socket, length, dispatch_get_global_queue(_server.dispatchQueuePriority, 0), ^(dispatch_data_t buffer, int error) { //GCDWebServerConnection 处理GCDWebServerMatchBlock和GCDWebServerAsyncProcessBlock self->_request = self->_handler.matchBlock(requestMethod, requestURL, requestHeaders, requestPath, requestQuery); ... _handler.asyncProcessBlock(request, [completion copy]);
在 LocalServer 的使用上,也要注意端口的选择(ports used by Apple),以及前后台切换时 suspendInBackground 的设置和业务处理。
3. JavaScript Open Api
随着 App 业务的不断发展,单纯的 Web 加载与渲染无法满足复杂的交互逻辑,如拍照、音视频、蓝牙、定位等,同时 App 内也需要统一的登录态、统一的分享逻辑以及支付逻辑等,所以针对第三方的 Web 页面,Native 需要注册相应的 JavaScript 接口供 Web 使用。
对于 Api 需要提供的能力、接口设计和文档规范,不同的业务逻辑和团队代码风格会有不同的定义,微信 JS-SDK 说明文档就是一个很好的例子。而脱离 JavaScript Open Api 对外的接口设计和封装,在内部的实现上也有一些通用的关键因素,这里简单列举几个:
注入方式和时机
对于 JavaScript 文件的注入,最简单的就是将 JS 文件打包到项目中,使用 WKWebView 提供的系统函数进行注入。这种方式无需网络加载,可以合理地选择注入时机,但是无法动态地进行修改和调整。而对于这部分业务需求需要经常调整的 App 来说,也可以把文件存储到 CDN,通过模板替换或者和 Web 合作者约定,在 Web 的 HTML 中通过 URL 的方式进行加载,这种方式虽然动态化程度较高,但是需要合作方的配合,同时对于 JS Api 也不能做到拆分地注入。
针对上面的两种方式的不足,一个较为合理的方式是 JavaScript 文件采用本地注入的方式,同时建立资源的动态更新系统(上文)。这样一方面支持了动态更新,同时也无需合作方的配合,对于不同的业务场景也可以拆分不同的 Api 进行注入,保证安全。
安全控制
JavaScript Open Api 设计实现的另一个重要方面,就是安全性的控制。由于完整的 Api 需要支持 Native 登录、Cookies 等较为敏感的信息获取,同时也支持一些对 UI 和体验影响较多的功能,如页面跳转、分享等,所以 App 需要一套权限分级的逻辑控制 Web 相关的接口调用,保证体验和安全。
常规的做法就是对 JavaScript Open Api 建立分级的管理,不同权限的 Web 页面只能调用各自权限内的接口。客户端通过 Domain 进行分级,同时支持动态拉取权限 Domain 白名单,灵活地配置 Web 页面的权限。在此基础上 App 内部也可以通过业务逻辑划分,在 Native 层面使用不同的容器加载页面,而容器根据业务逻辑的不同,注入不同的 JS 文件进行 Api 权限控制。
回顾一下,本文聚焦 iOS 开发和 Web 开发的交叉点,内容涉及到 iOS 开发中全部的 Web 知识,涵盖从基础使用到 WebKit、从 JSCore 到大前端、从 Web 优化到业务扩展等方面,希望通过这样简要的介绍,帮助开发者一窥 Hybrid 和大前端的构想,如果觉得本文对你有所帮助,欢迎点赞。
作者介绍
朱德权,个人 GitHub:https://github.com/dequan1331。
原文链接地址: https://developer.baidu.com/topic/show/290637
云计算
2020-02-25 15:20:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
##部署
解压安装包 tar xf etcd-v3.3.12-linux-amd64.tar.gz cd etcd-v3.3.12-linux-amd64 mv etcd* /usr/bin/ chmod +x /usr/bin/etcd*
运行Etcd etcd --name 'etcd' --data-dir '/var/lib/etcd/default.etcd' \ --listen-client-urls "http://10.0.102.211:2379,http://127.0.0.1:2379" \ --advertise-client-urls http://10.0.102.211:2379,http://127.0.0.1:2379& 【见图1】
检查状态 etcdctl --endpoints http://10.0.102.169:2379 cluster-health 【见图2】 etcdctl --endpoints http://10.0.102.169:2379 mk /coreos.com/network/config '{ "Network" : "10.88.0.0/16" , "SubnetLen" : 24 , "SubnetMin" : "10.88.1.0" , "SubnetMax" : "10.88.50.0" , "Backend" : { "Type" : "vxlan" }}' 【见图3】 检查 etcdctl --endpoints http://10.0.102.169:2379 ls /coreos.com/network/config etcdctl --endpoints http://10.0.102.169:2379 get /coreos.com/network/config 【见图4】
云计算
2020-02-23 22:06:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
作者 | 声东  阿里云售后技术专家
导读 :不知道大家有没有意识到一个现实:大部分时候,我们已经不像以前一样,通过命令行,或者可视窗口来使用一个系统了。
前言
现在我们上微博、或者网购,操作的其实不是眼前这台设备,而是一个又一个集群。通常,这样的集群拥有成百上千个节点,每个节点是一台物理机或虚拟机。集群一般远离用户,坐落在数据中心。为了让这些节点互相协作,对外提供一致且高效的服务,集群需要操作系统。Kubernetes 就是这样的操作系统。
比较 Kubernetes 和单机操作系统,Kubernetes 相当于内核,它负责集群软硬件资源管理,并对外提供统一的入口,用户可以通过这个入口来使用集群,和集群沟通。
而运行在集群之上的程序,与普通程序有很大的不同。这样的程序,是“关在笼子里”的程序。它们从被制作,到被部署,再到被使用,都不寻常。我们只有深挖根源,才能理解其本质。
“关在笼子里”的程序
代码
我们使用 go 语言写了一个简单的 web 服务器程序 app.go,这个程序监听在 2580 这个端口。通过 http 协议访问这个服务的根路径,服务会返回 "This is a small app for kubernetes..." 字符串。 package main import ( "github.com/gorilla/mux" "log" "net/http" ) func about(w http.ResponseWriter, r *http.Request) { w.Write([]byte("This is a small app for kubernetes...\n")) } func main() { r := mux.NewRouter() r.HandleFunc("/", about) log.Fatal(http.ListenAndServe("0.0.0.0:2580", r)) }
使用 go build 命令编译这个程序,产生 app 可执行文件。这是一个普通的可执行文件,它在操作系统里运行,会依赖系统里的库文件。 # ldd app linux-vdso.so.1 => (0x00007ffd1f7a3000) libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f554fd4a000) libc.so.6 => /lib64/libc.so.6 (0x00007f554f97d000) /lib64/ld-linux-x86-64.so.2 (0x00007f554ff66000)
“笼子”
为了让这个程序不依赖于操作系统自身的库文件,我们需要制作容器镜像,即隔离的运行环境。Dockerfile 是制作容器镜像的“菜谱”。我们的菜谱就只有两个步骤,下载一个 centos 的基础镜像,把 app 这个可执行文件放到镜像中 /usr/local/bin 目录中去。 FROM centos ADD app /usr/local/bin
地址
制作好的镜像存再本地,我们需要把这个镜像上传到镜像仓库里去。这里的镜像仓库,相当于应用商店。我们使用阿里云的镜像仓库,上传之后镜像地址是: registry.cn-hangzhou.aliyuncs.com/kube-easy/app:latest
镜像地址可以拆分成四个部分:仓库地址/命名空间/镜像名称:镜像版本。显然,镜像上边的镜像,在阿里云杭州镜像仓库,使用的命名空间是 kube-easy,镜像名:版本是 app:latest。至此,我们有了一个可以在 Kubernetes 集群上运行的、“关在笼子里”的小程序。
得其门而入
入口
Kubernetes 作为操作系统,和普通的操作系统一样,有 API 的概念。有了 API,集群就有了入口;有了 API,我们使用集群,才能得其门而入。Kubernetes 的 API 被实现为运行在集群节点上的组件 API Server。这个组件是典型的 web 服务器程序,通过对外暴露 http(s) 接口来提供服务。
这里我们创建一个阿里云 Kubernetes 集群。登录集群管理页面,我们可以看到 API Server 的公网入口。 API Server 内网连接端点: https://xx.xxx.xxx.xxx:6443
双向数字证书验证
阿里云 Kubernetes 集群 API Server 组件,使用基于 CA 签名的双向数字证书认证来保证客户端与 api server 之间的安全通信。这句话很绕口,对于初学者不太好理解,我们来深入解释一下。
从概念上来讲,数字证书是用来验证网络通信参与者的一个文件。这和学校颁发给学生的毕业证书类似。在学校和学生之间,学校是可信第三方 CA,而学生是通信参与者。如果社会普遍信任一个学校的声誉的话,那么这个学校颁发的毕业证书,也会得到社会认可。参与者证书和 CA 证书可以类比毕业证和学校的办学许可证。
这里我们有两类参与者,CA 和普通参与者;与此对应,我们有两种证书,CA 证书和参与者证书;另外我们还有两种关系,证书签发关系以及信任关系。这两种关系至关重要。
我们先看签发关系。如下图,我们有两张 CA 证书,三个参与者证书。
其中最上边的 CA 证书,签发了两张证书,一张是中间的 CA 证书,另一张是右边的参与者证书;中间的 CA 证书,签发了下边两张参与者证书。这六张证书以签发关系为联系,形成了树状的证书签发关系图。
然而,证书以及签发关系本身,并不能保证可信的通信可以在参与者之间进行。以上图为例,假设最右边的参与者是一个网站,最左边的参与者是一个浏览器,浏览器相信网站的数据,不是因为网站有证书,也不是因为网站的证书是 CA 签发的,而是因为浏览器相信最上边的 CA,也就是信任关系。
理解了 CA(证书),参与者(证书),签发关系,以及信任关系之后,我们回过头来看“基于 CA 签名的双向数字证书认证”。客户端和 API Server 作为通信的普通参与者,各有一张证书。而这两张证书,都是由 CA 签发,我们简单称它们为集群 CA 和客户端 CA。客户端信任集群 CA,所以它信任拥有集群 CA 签发证书的 API Server;反过来 API Server 需要信任客户端 CA,它才愿意与客户端通信。
阿里云 Kubernetes 集群,集群 CA 证书,和客户端 CA 证书,实现上其实是一张证书,所以我们有这样的关系图。
KubeConfig 文件
登录集群管理控制台,我们可以拿到 KubeConfig 文件。这个文件包括了客户端证书,集群 CA 证书,以及其他。证书使用 base64 编码,所以我们可以使用 base64 工具解码证书,并使用 openssl 查看证书文本。 首先,客户端证书的签发者 CN 是集群 id c0256a3b8e4b948bb9c21e66b0e1d9a72,而证书本身的 CN 是子账号 252771643302762862; Certificate: Data: Version: 3 (0x2) Serial Number: 787224 (0xc0318) Signature Algorithm: sha256WithRSAEncryption Issuer: O=c0256a3b8e4b948bb9c21e66b0e1d9a72, OU=default, CN=c0256a3b8e4b948bb9c21e66b0e1d9a72 Validity Not Before: Nov 29 06:03:00 2018 GMT Not After : Nov 28 06:08:39 2021 GMT Subject: O=system:users, OU=, CN=252771643302762862 其次,只有在 API Server 信任客户端 CA 证书的情况下,上边的客户端证书才能通过 API Server 的验证。kube-apiserver 进程通过 client-ca-file 这个参数指定其信任的客户端 CA 证书,其指定的证书是 /etc/kubernetes/pki/apiserver-ca.crt。这个文件实际上包含了两张客户端 CA 证书,其中一张和集群管控有关系,这里不做解释,另外一张如下,它的 CN 与客户端证书的签发者 CN 一致; Certificate: Data: Version: 3 (0x2) Serial Number: 787224 (0xc0318) Signature Algorithm: sha256WithRSAEncryption Issuer: O=c0256a3b8e4b948bb9c21e66b0e1d9a72, OU=default, CN=c0256a3b8e4b948bb9c21e66b0e1d9a72 Validity Not Before: Nov 29 06:03:00 2018 GMT Not After : Nov 28 06:08:39 2021 GMT Subject: O=system:users, OU=, CN=252771643302762862 再次,API Server 使用的证书,由 kube-apiserver 的参数 tls-cert-file 决定,这个参数指向证书 /etc/kubernetes/pki/apiserver.crt。这个证书的 CN 是 kube-apiserver,签发者是 c0256a3b8e4b948bb9c21e66b0e1d9a72,即集群 CA 证书; Certificate: Data: Version: 3 (0x2) Serial Number: 2184578451551960857 (0x1e512e86fcba3f19) Signature Algorithm: sha256WithRSAEncryption Issuer: O=c0256a3b8e4b948bb9c21e66b0e1d9a72, OU=default, CN=c0256a3b8e4b948bb9c21e66b0e1d9a72 Validity Not Before: Nov 29 03:59:00 2018 GMT Not After : Nov 29 04:14:23 2019 GMT Subject: CN=kube-apiserver 最后,客户端需要验证上边这张 API Server 的证书,因而 KubeConfig 文件里包含了其签发者,即集群 CA 证书。对比集群 CA 证书和客户端 CA 证书,发现两张证书完全一样,这符合我们的预期。 Certificate: Data: Version: 3 (0x2) Serial Number: 786974 (0xc021e) Signature Algorithm: sha256WithRSAEncryption Issuer: C=CN, ST=ZheJiang, L=HangZhou, O=Alibaba, OU=ACS, CN=root Validity Not Before: Nov 29 03:59:00 2018 GMT Not After : Nov 24 04:04:00 2038 GMT Subject: O=c0256a3b8e4b948bb9c21e66b0e1d9a72, OU=default, CN=c0256a3b8e4b948bb9c21e66b0e1d9a72
访问
理解了原理之后,我们可以做一个简单的测试:以证书作为参数,使用 curl 访问 api server,并得到预期结果。 # curl --cert ./client.crt --cacert ./ca.crt --key ./client.key https://xx.xx.xx.xxx:6443/api/ { "kind": "APIVersions", "versions": [ "v1" ], "serverAddressByClientCIDRs": [ { "clientCIDR": "0.0.0.0/0", "serverAddress": "192.168.0.222:6443" } ] }
择优而居
两种节点,一种任务
如开始所讲,Kubernetes 是管理集群多个节点的操作系统。这些节点在集群中的角色,却不必完全一样。Kubernetes 集群有两种节点:master 节点和 worker 节点。
这种角色的区分,实际上就是一种分工:master 负责整个集群的管理,其上运行的以集群管理组件为主,这些组件包括实现集群入口的 api server;而 worker 节点主要负责承载普通任务。
在 Kubernetes 集群中,任务被定义为 pod 这个概念。pod 是集群可承载任务的原子单元,pod 被翻译成容器组,其实是意译,因为一个 pod 实际上封装了多个容器化的应用。原则上来讲,被封装在一个 pod 里边的容器,应该是存在相当程度的耦合关系。
择优而居
调度算法需要解决的问题,是替 pod 选择一个舒适的“居所”,让 pod 所定义的任务可以在这个节点上顺利地完成。
为了实现“择优而居”的目标,Kubernetes 集群调度算法采用了两步走的策略: 第一步,从所有节点中排除不满足条件的节点,即预选; 第二步,给剩余的节点打分,最后得分高者胜出,即优选。
下面我们使用文章开始的时候制作的镜像,创建一个 pod,并通过日志来具体分析一下,这个 pod 怎么样被调度到某一个集群节点。
Pod 配置
首先,我们创建 pod 的配置文件,配置文件格式是 json。这个配置文件有三个地方比较关键,分别是镜像地址,命令以及容器的端口。 { "apiVersion": "v1", "kind": "Pod", "metadata": { "name": "app" }, "spec": { "containers": [ { "name": "app", "image": "registry.cn-hangzhou.aliyuncs.com/kube-easy/app:latest", "command": [ "app" ], "ports": [ { "containerPort": 2580 } ] } ] } }
日志级别
集群调度算法被实现为运行在 master 节点上的系统组件,这一点和 api server 类似。其对应的进程名是 kube-scheduler。kube-scheduler 支持多个级别的日志输出,但社区并没有提供详细的日志级别说明文档。查看调度算法对节点进行筛选、打分的过程,我们需要把日志级别提高到 10,即加入参数 --v=10。 kube-scheduler --address=127.0.0.1 --kubeconfig=/etc/kubernetes/scheduler.conf --leader-elect=true --v=10
创建 Pod
使用 curl,以证书和 pod 配置文件等作为参数,通过 POST 请求访问 api server 的接口,我们可以在集群里创建对应的 pod。 # curl -X POST -H 'Content-Type: application/json;charset=utf-8' --cert ./client.crt --cacert ./ca.crt --key ./client.key https://47.110.197.238:6443/api/v1/namespaces/default/pods -d@app.json
预选
预选是 Kubernetes 调度的第一步,这一步要做的事情,是根据预先定义的规则,把不符合条件的节点过滤掉。不同版本的 Kubernetes 所实现的预选规则有很大的不同,但基本的趋势,是预选规则会越来越丰富。
比较常见的两个预选规则是 PodFitsResourcesPred 和 PodFitsHostPortsPred。前一个规则用来判断,一个节点上的剩余资源,是不是能够满足 pod 的需求;而后一个规则,检查一个节点上某一个端口是不是已经被其他 pod 所使用了。
下图是调度算法在处理测试 pod 的时候,输出的预选规则的日志。这段日志记录了预选规则 CheckVolumeBindingPred 的执行情况。某些类型的存储卷(PV),只能挂载到一个节点上,这个规则可以过滤掉不满足 pod 对 PV 需求的节点。
从 app 的编排文件里可以看到,pod 对存储卷并没有什么需求,所以这个条件并没有过滤掉节点。
优选
调度算法的第二个阶段是优选阶段。这个阶段,kube-scheduler 会根据节点可用资源及其他一些规则,给剩余节点打分。
目前,CPU 和内存是调度算法考量的两种主要资源,但考量的方式并不是简单的,剩余 CPU、内存资源越多,得分就越高。
日志记录了两种计算方式:LeastResourceAllocation 和 BalancedResourceAllocation。 前一种方式计算 pod 调度到节点之后,节点剩余 CPU 和内存占总 CPU 和内存的比例,比例越高得分就越高; 第二种方式计算节点上 CPU 和内存使用比例之差的绝对值,绝对值越大,得分越少。
这两种方式,一种倾向于选出资源使用率较低的节点,第二种希望选出两种资源使用比例接近的节点。这两种方式有一些矛盾,最终依靠一定的权重来平衡这两个因素。
除了资源之外,优选算法会考虑其他一些因素,比如 pod 与节点的亲和性,或者如果一个服务有多个相同 pod 组成的情况下,多个 pod 在不同节点上的分散程度,这是保证高可用的一种策略。
得分
最后,调度算法会给所有的得分项乘以它们的权重,然后求和得到每个节点最终的得分。因为测试集群使用的是默认调度算法,而默认调度算法把日志中出现的得分项所对应的权重,都设置成了 1,所以如果按日志里有记录得分项来计算,最终三个节点的得分应该是 29,28 和 29。
之所以会出现日志输出的得分和我们自己计算的得分不符的情况,是因为日志并没有输出所有的得分项,猜测漏掉的策略应该是 NodePreferAvoidPodsPriority,这个策略的权重是 10000,每个节点得分 10,所以才得出最终日志输出的结果。
结束语
在本文中,我们以一个简单的容器化 web 程序为例,着重分析了客户端怎么样通过 Kubernetes 集群 API Server 认证,以及容器应用怎么样被分派到合适节点这两件事情。
在分析过程中,我们弃用了一些便利的工具,比如 kubectl,或者控制台。我们用了一些更接近底层的小实验,比如拆解 KubeConfig 文件,再比如分析调度器日志来分析认证和调度算法的运作原理。希望这些对大家进一步理解 Kubernetes 集群有所帮助。
架构师成长系列直播
“ 阿里巴巴云原生 关注微服务、Serverless、容器、Service Mesh 等技术领域、聚焦云原生流行技术趋势、云原生大规模的落地实践,做最懂云原生开发者的技术圈。”
云计算
2020-02-17 09:50:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
作者 | Rafal 
导读 :Helm 是 Kubernetes 的一个软件包管理器。两个月前,它发布了第三个主要版本,Helm 3。在这一新版本中,有许多重大变化。本文作者将介绍自己认为最关键的 5 个方面。
移除了 Tiller
Helm 最终移除了其服务器端组件,Tiller。现在,它完全没有代理。Tiller 之前是一个运行在 Kubernetes 上的小型应用程序,它用于监听 Helm 命令并处理设置 Kubernetes 资源的实际工作。
这是 Helm3 中最重大的更改。为什么 Tiller 的移除备受关注呢?首先,Helm 应该是一种在 Kubernetes 配置上的模板机制。那么,为什么需要在服务器上运行某些代理呢?
Tiller 本身也存在一些问题,因为它需要集群管理员的 ClusterRole 才能创建。因此,假设你要在 Google Cloud Platform 中启动的 Kubernetes 集群上运行 Helm 应用程序。首先,你需要启动一个新的 GKE 集群,然后使用 helm init 初始化 Helm,然后…发现它失败了。
这种情况之所以会发生是因为,在默认状态下,你没有给你的 kubectl 上下文分配管理员权限。现在你了解到了这一点,开始搜索为分配管理员权限的 magic 命令。这一系列操作下来,也许你已经开始怀疑 Helm 是否真的是一个不错的选择。
此外,由于 Tiller 使用的访问权限与你在 kubectl 上下文中配置的访问权限不同。因此,你也许可以使用 Helm 创建应用程序,但你可能无法使用 kubectl 创建该程序。这一情况如果没排查出来,看起来感觉像是安全漏洞。
幸运的是,现在 Tiller 已经被完全移除,Helm 现在是一个客户端工具。这一更改会导致以下结果: Helm 使用与 kubectl 上下文相同的访问权限; 你无需再使用 helm init 来初始化 Helm; Release Name 位于命名空间中。
Helm 3 一直保持不变的是:它应该只是一个在 Kubernetes API 上执行操作的工具。如此,如果你可以使用纯粹的 kubectl 命令执行某项操作,那么也可以使用 helm 执行该操作。
分布式仓库以及 Helm Hub
Helm 命令可以从远程仓库安装 Chart。在 Helm 3 之前,它通常使用预定义的中心仓库,但你也能够添加其他仓库。但是从现在开始,Helm 将其仓库模型从集中式迁移到分布式。这意味着两个重要的改变: 预定义的中心仓库被移除; Helm Hub(一个发现分布式 chart 仓库的平台)被添加到 helm search。
为了能够更好地理解这一改变,我给你们一个示例。在 Helm 3 之前,如果你想要安装一个 Hazelcast 集群,你需要执行以下命令: $ helm2 install --name my-release stable/hazelcast
现在,这个命令不起作用了。你需要先添加远程仓库才能进行安装。这是因为这里不再存在一个预定义中心仓库。要安装 Hazelcast 集群,你首先需要添加其仓库然后安装 chart: $ helm3 repo add hazelcast https://hazelcast.github.io/charts/ $ helm3 repo update $ helm3 install my-release hazelcast/hazelcast
好消息是现在 Helm 命令可以直接在 Helm Hub 中寻找 Chart。例如,如果你想知道在哪个仓库中可以找到 Hazelcast,你只需执行以下命令即可: $ helm3 search hub hazelcast
以上命令列出在 Helm Hub 中所有分布式仓库中名称中包含 “hazelcast” 的 Chart。
现在,我来问你一个问题。移除掉中心仓库是进步还是退步?这有两种观点。第一种是 chart 维护者的观点。例如,我们维护 Hazelcast Helm Chart,而 Chart 中的每个更改都需要我们将其传播到中心仓库中。这项额外的工作使得中心仓库中的许多 Helm Chart 没有得到很好地维护。这一情况与我们在 Ubuntu/Debian 包仓库中所经历的很相似。你可以使用默认仓库,但它常常只有旧的软件包版本。
第二种观点来自 Chart 的使用者。对于他们来说,虽然现在安装一个 chart 比之前稍微困难了一些,但另一方面,他们能够从主要的仓库中安装到最新的 chart。
JSON Schema 验证
从 Helm 3 开始,chart 维护者可以为输入值定义 JSON Schema。这一功能的完善十分重要,因为迄今为止你可以在 values.yaml 中放入任何你所需的内容,但是安装的最终结果可能不正确或出现一些难以理解的错误消息。 例如,你在 port 参数中输入字符串而不是数字。那么你会收到以下错误: $ helm2 install --name my-release --set service.port=string-name hazelcast/hazelcast Error: release my-release failed: Service in version "v1" cannot be handled as a Service: v1.Service.Spec: v1.ServiceSpec.Ports: []v1.ServicePort: v1.ServicePort.Port: readUint32: unexpected character: �, error found in #10 byte of ...|","port":"wrong-name|..., bigger context ...|fault"},"spec":{"ports":[{"name":"hzport","port":"wrong-name","protocol": "TCP","targetPort":"hazelca|...
你不得不承认这个问题难以分析和理解。
此外,Helm 3 默认添加了针对 Kubernetes 对象的 OpenAPI 验证,这意味着发送到 Kubernetes API 的请求将会被检查是否正确。这对于 Chart 维护者来说,是一项重大利好。
Helm 测试
Helm 测试是一个小小的优化。尽管微小,但它也许实际上鼓励了维护者来写 Helm 测试以及用户在安装完每个 chart 之后执行 helm test 命令。在 Helm 3 之前,进行测试多少都显得有些奇怪: 此前测试作为 Pod 执行(好像需要一直运行);现在你可以将其定义为 Job;
测试 Pod 不会自动被移除(除非你使用 magic flag –cleanup),所以默认状态下,没有任何技巧,对于既定的版本你不能多次执行 helm test。但幸运的是,现在可以自动删除测试资源(Pod、Job)。
当然旧的测试版本也并非不能使用,只需要使用 Pod 并始终记得执行 helm test –cleanup。但也不得不承认,这一改进有助于提升测试体验。
命令行语法
最后一点是,Helm 命令语法有所改变。从积极的一面来看,我认为所有的改变都是为了让体验更好;从消极的方面看,这一语法不与之前的版本兼容。因此,现在编写有关如何使用 Helm 安装东西的步骤时,需要明确指出所使用的命令是用于 Helm 2 还是用于 Helm 3。
举个例子,从 helm install 开始说起。现在版本名称已经成为必填参数,尽管在 Helm 2 中你可以忽略它,名称也能够自动生成。如果在 Helm3 中要达成相同的效果,你需要添加参数 --generate-name。所以,使用 Helm 2 进行标准的安装应该如下: $ helm2 install --name my-release --set service.port=string- $ helm2 install --name my-release hazelcast/hazelcast
在 Helm 3 中,需要执行以下命令: $ helm3 install my-release hazelcast/hazelcast
还有另一个比较好的改变是,删除 Helm 版本后,无需添加— purge。简单地输入命令 helm uninstall 即可删除所有相关的资源。
还有一些其他改变,如一些命令被重命名(不过使用旧的名称作为别名),有一些命令则被删除(如 helm init)。如果你还想了解更多关于 Helm 命令语法更改的信息,请参考官方文档: https://helm.sh/docs/faq/#cli-command-renames
结  论
Helm 3 的发布,使得这一工具迈向一个新的阶段。作为用户,我十分喜欢 Helm 现在只是一个单纯的客户端工具。作为 Chart 维护者,Helm Hub 以及分布式仓库的方法深得我心。我希望能在未来看到更多更有意思的改变。
如果你想了解 Helm 3 中的所有变化,请查看官方文档: https://helm.sh/docs/faq/#changes-since-helm-2
本文转载自:RancherLabs, 点击查看原文 。 “ 阿里巴巴云原生 关注微服务、Serverless、容器、Service Mesh 等技术领域、聚焦云原生流行技术趋势、云原生大规模的落地实践,做最懂云原生开发者的技术圈。”
云计算
2020-02-12 11:59:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
Serverless 是炙手可热的技术,被认为是云计算发展的未来方向。尤其是在前端研发领域,使用 Node 开发云函数,可以让前端工程师更加专注于业务逻辑,实现全栈工程师的角色转变。
Serverless 的优势
技术 Leader 和架构师在进行技术选型时会关注很多指标, Serverless 贡献最大的就是  研发交付速度(Time to Market)  和  成本(Cost) 。
研发交付速度方面,衡量的指标是 Time to Market,是从需求产出到上线所用的总时长,Serverless 在这方面的优势在技术和团队协作两个视角上均有体现。
一是技术视角。 有一种观点称 Serverless 是一种很简单的技术,我对这种观点并不完全同意。Serverless 架构让用户和底层架构的关系发生了变化,之前开发者需要关注核心业务逻辑、运维和底层架构的治理,在 Serverless 架构中底层的部分由 Serverless 架构提供方来解决。从整个应用系统的角度来看,系统架构的难度和复杂度并没有实质简化。
这里我们不展开讲 Serverless 架构的底层实现细节。 只需要了解一点:Serverless 底层架构做的事情越多,业务层面需要关注的架构和运维工作就越少,因为做的工作少了,所以交付的时间就更快了。
**二是团队协作视角。在 Serverless 的模式下,全栈开发的工作模式会执行得更加顺畅。**我们知道,前后端分离确实是一种很好的架构模式:细化了分工,降低了耦合,提升了复用。但随之而来的问题,是团队间的沟通成本、KPI 目标的差异所带来的各种催排期、接口确认以及联调测试。不少技术团队用全栈开发的模式来解决这些问题, Serverless 下不需要在架构和技术栈花费过多精力,Runtime 和语言也没有强制依赖,而是完全面向业务,每个前端工程师都可以是全栈的。
另一个优势就是 Serverless 会大大降低成本,体现在计算资源和人力两个层面。
在计算资源的成本方面,主要体现在弹性扩缩容量,按需付费。在传统的计算资源预算时,往往为了能抗住峰值流量,系统容量都有 Buffer,说白了就是日常的浪费。
在 Serverless 模式下,当业务代码上线后,一分钱都不需要支付。只有当真实请求和流量过来了,平台才会根据请求量,瞬时拉起对应数量的函数实例,去接收请求和执行业务代码,此时才需要为真正的代码执行所消耗的资源付费。**No Pay for Idle ,从会计学的角度,Serverless 让计算资源从固定成本变成了可变成本。**这种付费模式对于那种流量波动很大的业务优势明显。
还要说明的是人力成本。很多技术 Leader 总抱怨人手不够,也许真实情况并非如此,只是没有足够比例的人投入到业务功能的开发和迭代上,而是去做了架构、底层等必要的支撑性工作。我承认这些工作确实非常有挑战、非常必要,也非常重要。在 Serverless 模式下,由于不再需要关注底层架构,所以缩小这部分的工作量和人力占比,就有了更多的工程师可以放在核心业务上,多做迭代,从而加速产品功能的研发。这不是更高的 ROI 吗?
Serverless 的适用场景
Serverless 适用于事件触发的场景。当某个事件发生时,拉起并调用 Serverless 云函数,比如文件上传、消息队列中的消息事件、定时器事件,也可以是 IoT 设备的某个事件。还可以用于一些文件处理,比如图像处理、音视频处理和日志分析等场景。
当然,这些事件也包括 HTTP 请求事件,这是 Serverless 的一个很大的适用场景—— HTTP Service,主要实现基于 HTTP 应用的后端服务,比如 REST API、BFF 和 SSR 服务,以及业务逻辑的实现。
我主要关注 Serverless 在 HTTP 场景下的应用。这也是和前端工程师结合最紧密的部分。小到为小游戏、运营活动提供后端的支持,大到整个 App 或站点的 REST API、BFF,或是 H5 页面的 SSR,都是 Serverless 适用的场景。
Serverless 对前端开发者的意义
Serverless 的诸多优势业内有很多讨论,也有不少文章谈及。我想聚焦到前端开发者身上来说一说,**Serverless 能够帮助前端工程师实现真全栈的梦想。**可能有人会质疑,为什么你又提出一个真全栈,和之前的全栈有什么区别吗?
我先明确一下真全栈的定义:如何判断一个工程师是真全栈工程师?
当公司有了一堆产品功能需求,招了一个程序员张全占,如果他能从 0 到 1 把需求做成产品,那才叫真全栈。如果张全占完成了前端功能开发、后端开发以及数据库开发,实现了所有的需求功能,并且部署到对应的服务上,就完事了,那么问题也就来了:服务挂了谁来重启?环境稳定性谁来做?日志把磁盘写满了谁来清理?定时任务怎么搞?产品突然火爆了,流量一夜间突然扩大了十几倍的时候(产品经理狂喜中),谁负责扩容?这些问题虽然不是核心业务需求,却是每一个线上产品都必须考虑的东西,否则只能称为功能集合,不能称之为产品。
Serverless 架构的出现,将刚才说到的一些非核心业务逻辑,以及运维相关的事情给“屏蔽”了。前端工程师张全占只需关注前台功能、后台功能和数据这些核心的业务逻辑,就可以独立做出产品。例如目前的微信小程序云开发就是 Serverless 式的,开发者完全不用关注底层架构。
Serverless 对前端工程师群体来说是一个机会。让一个前端工程师能够得到独立负责某些产品研发的机会,完成某些产品从需求到上线的从 0 到 1 的机会,一个回归到互联网研发工程师角色的机会。 我希望所有的前端工程师都有机会成为 Serverless 工程师,有机会独立负责研发整个产品。
采用 Serverless 的准备
总体上来说,采用 Serverless 不需要工程师大量的学习和准备过程。
Serverless 本身就是在现有的架构中做减法,减去那些服务器的管理和配置工作。当然在具体落地的时候,还是有一些准备工作要做:
首先是明确目标,开发者在了解 Serverless 之后,应该去思考对于自身业务和开发架构,采用 Serverless 是为了解决什么问题?想取得哪方面的提升?没有一种技术是为了用而用,都是针对具体场景解决具体问题。这是第一个需要搞明白的。
明确了目标之后,接着是 Serverless 模式下架构的一些设计工作。与传统的开发模式一样,系统设计的工作量是根据业务的复杂程度决定的。对于复杂业务逻辑来说,在开发之前需要明确有多少个云函数,每个云函数的输入输出定义、采用哪些 BaaS 后端服务,都需要提前设计规划好。
特别要说明的是,这些设计和非 Serverless 并没有什么本质上的不同,Serverless 云函数也不是神秘莫测的。简单理解,它所提供的就是一个语言的 Runtime。在非 Serverless 架构下如何执行的代码,Serverless 架构下还是那样执行。如果业务是基于 Express 或者 koa 这类应用框架,那么 Serverless 云函数下,还是直接使用这些框架即可。
最后是一些实施上的准备,以腾讯云函数为例,只要是写过代码的,花小半天时间阅读一些基础文档、教程,或者是跟着 Demo 走一遍,就可以立刻开始写代码,几乎没有什么门槛和不同。要敲黑板强调的是,别忘记了工程化和 CI/CD 方面的考虑,尤其是和现有研发流程的结合。这块有一些小小的工作量,毕竟是开发模式的升级,但基于云函数提供的 CLI 和 SDK 都很容易实现。
Serverless 和云函数的关系
Serverless 架构由两部分构成,分别是 FaaS(Functions as a Service)和 BasS(Backend as a Sevice)。其中 FaaS 就是指云函数,它是一种新的算力组织和提供方式,它让用户不再需要关心服务器的管理和配置,只用专注于核心业务逻辑业务代码的编写。BaaS 指的是一些服务化的后端功能,包括数据库 / 对象存储、账户权鉴、消息队列、社交媒体整合和 AI 能力等,这些服务和接口在 FaaS 层使用相应的 SDK 或 API 来连接和调用。
FaaS+BaaS 的组合,构成了 Serverless 无服务器架构,免除了所有运维性操作,让企业和开发者可以更加专注于核心业务的开发,实现快速上线和迭代,把握业务发展的节奏。
由此可见,云函数是 Severless 架构中的算力部分,是实现 Severless 架构的基础计算资源。在 Severless 架构下的业务系统中,因业务功能、需求场景不同,所需的 BaaS 后端服务也可能各不相同,但业务逻辑都需要通过云函数来实现。
具体案例
刚才也提到 Serverless 本身有很多很多的应用场景,这个问题在不同的 Serverless 的场景下,答案也是不同的。
如果业务需求是基于类似于 Express、koa 的应用框架来实现的,那么在设计上,基本没有任何区别。Serverless 云函数可以很好地支持这些应用框架,只是部署方式不同而已。
如果需求场景不需要任何应用框架,直接使用原生代码,在 Serverless 架构下进行设计时,需要以函数为粒度来考虑,将函数作为业务中的最小功能单元。
还有一个场景使用 Serverless 和不使用就有很大的不同——企业上云。
现在很多企业应用都做应用上云,上云其实是一件非常有技术门槛的事情。可能需要上云的代码只有几百行,但传统上云绝不是上传部署几百行代码那么简单(估计很多工程师看到 Kubernetes 那几本厚书的时候就已经快疯了)。这个过程需要专业的、有经验的工程师,花费大量的工作,才能把业务系统迁移到云上。
Serverless 下的体验就非常不同,因为无服务器架构,所以不需要关注虚机或者容器配置和治理工作,基本上只用上传代码就完成了上云。
Serverless 的未来演化
从以往的历史来看,技术的演化还是存在一些一般规律的。
首先我预测 Serverless 生态一定会趋于繁荣。一个技术很有优势,相关的社区贡献,以及周边的支持就越强大,用的人就越多;用的人越多,这个技术就越火,类似于经济学里的有效市场理论。最近 Serverless 的发展很快,可能大家看到这篇内容的时候,我们的 Serverless DB 产品已经发布了,就是开发者连数据库的存在都不需要关注了。Serverless 的使用者会越来越多,同时生态里的贡献者也会更多,整个生态也会更加繁荣。
第二个方向是 Serverless 的标准化。当生态繁荣之后,对于标准化的需求就变得非常强烈了。国内外各家云都有了自己的 Serverless 解决方案,对开发者隐藏了底层基础设置。但是各家的接口、实现还是不一样。试想一下,开发者在国内云上用 Serverless 实现的代码,在做国际化的时候,要迁移到另一个云厂商,却发现完全无法平滑迁移是什么感受?公司内两个技术团队如何在 Serverless 的架构下复用功能和代码?如何能够用统一的标准或者框架来构建应用?Serverless 开发需要一些标准,或是某一种框架来适配各个云厂商之间的不同实现和接口,很可能是 Serverless 接下来的发展方向。
Serverless Framework 30 天试用计划
我们诚邀您来体验最便捷的 Serverless 开发和部署方式。在试用期内,相关联的产品及服务均提供免费资源和专业的技术支持,帮助您的业务快速、便捷地实现 Serverless! 详情可查阅: Serverless Framework 试用计划
One More Thing
3 秒你能做什么?喝一口水,看一封邮件,还是 —— 部署一个完整的 Serverless 应用? 复制链接至 PC 浏览器访问: https://serverless.cloud.tencent.com/deploy/express
3 秒极速部署,立即体验史上最快的 Serverless HTTP 实战开发! 传送门: GitHub: github.com/serverless 官网: serverless.com
欢迎访问: Serverless 中文网 ,您可以在 最佳实践 里体验更多关于 Serverless 应用的开发! 推荐阅读: 《Serverless 架构:从原理、设计到项目实战》
云计算
2020-02-12 11:41:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 课程目录: 01、课程介绍 02、背景说明 03、课程目标和主要内容 04、课程案例需求 05、课程补充说明 06、为何采用微服务架构? 07、架构设计和技术栈选型 08、数据和接口模型设计:账户服务 09、数据和接口模型设计:业务服务 10、Dubbo、SpringCloud和Kubernetes该如何选型(上) 11、Dubbo、SpringCloud和Kubernetes该如何选型(中) 12、Dubbo、SpringCloud和Kubernetes该如何选型(下) 13、技术中台到底讲什么? 14、Staffjoy项目结构组织 15、谷歌为何采用单体仓库(Mono、Repo)? 16、微服务接口参数校验为何重要? 17、如何实现统一异常处理? 18、DTO和DMO为什么要互转? 19、如何实现基于Feign的强类型接口? 20、为什么框架层就要考虑分环境配置? 21、异步处理为何要复制线程上下文信息? 22、为你的接口添加Swagger文档 23、主流微服务框架概览 24、网关和BFF是如何演化出来的(上) 25、网关和BFF是如何演化出来的(下) 26、网关和反向代理是什么关系? 27、网关需要分集群部署吗? 28、如何设计一个最简网关? 29、Faraday网关代码解析(上) 30、Faraday网关代码解析(下) 31、生产级网关需要考虑哪些环节? 32、主流开源网关概览 33、安全认证架构演进:单块阶段(上) 34、安全认证架构演进:单块阶段(下) 35、安全认证架构演进:微服务阶段 36、基于JWT令牌的安全认证架构 37、JWT的原理是什么? 38、JWT有哪两种主要流程? 39、Staffjoy安全认证架构和SSO 40、用户认证代码剖析 41、服务调用鉴权代码剖析 42、如何设计用户角色鉴权? 43、SpringBoot微服务测试该如何分类? 44、什么是契约驱动测试? 45、什么是测试金字塔? 46、单元测试案例分析 47、集成测试案例分析 48、组件测试案例分析 49、Mock、vs、Spy 50、何谓生产就绪(Production、Ready)? 51、SpringBoot如何实现分环境配置 52、Apollo、vs、SpringCloud、Config、vs、K8s、ConfigMap 53、CAT、vs、Zipkin、vs、Skywalking(上) 54、CAT、vs、Zipkin、vs、Skywalking(下) 55、结构化日志和业务审计日志 56、集中异常监控和Sentry 57、EFK&Prometheus&Skywalking+K8s集成架构 58、本地开发部署架构和软件需求 59、手工服务部署和测试(上) 60、手工服务部署和测试(中) 61、手工服务部署和测试(下) 62、SkyWalking调用链监控实验 63、Docker和DockerCompose简介 64、容器镜像构建Dockerfile解析 65、DockerCompose服务部署文件剖析 66、将Staffjoy部署到本地DockerCompose环境(上) 67、将Staffjoy部署到本地DockerCompose环境(下) 68、到底什么是云原生架构 68、到底什么是云原生架构? 69、Kubernetes背景和架构 70、Kubernetes有哪些基本概念?(上)
下载地址: Spring Boot与Kubernetes云原生微服务实践
云计算
2020-02-07 22:01:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
docker本身提供有 attach 、 exec 等方法进入容器中,本身却没有支持远程管理容器的方法。本文介绍为centos基础镜像添加SSH支持
手动构建 docker pull centos:7 :下载一个contos镜像
docker run -it --name ssh_cenots7 docker.io/centos:7 bash :运行并进入centos7容器
yum install openssh-server :下载并安装 penssh-server
ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key;ssh-keygen -t ecdsa -f /etc/ssh/ssh_host_ecdsa_key;ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key :配置密钥文件
vi /etc/pam.d/sshd :注销 pam_loginuid.so ,关闭pam登陆限制
passwd root :设置修改root密码或者添加账户
创建run.sh文件,并添加运行权限, chmod +x run.sh #!/bin/bash /usr/sbin/sshd -D docker commit ssh_cenots7 sshd:centos7 :将容器制作为镜像
docker run -p 2222:22 -d sshd:centos7 /run.sh :运行sshd镜像,之后可以使用ip地址连接2222端口ssh进入docker容器
使用dockerfile构建 FROM centos:7 #作者信息 MAINTAINER shuangmu RUN yum install -y openssh-server RUN ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key;ssh-keygen -t ecdsa -f /etc/ssh/ssh_host_ecdsa_key;ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key #取消pam登陆限制 RUN sed -ri 's/session required pam_loginuid.so/#session required pam_loginuid.so/g' /etc/pam.d/sshd #修改root密码 RUN echo "root:root" | chpasswd COPY run.sh /run.sh RUN chmod 755 /run.sh #开放端口 EXPOSE 22 CMD ["/run.sh"]
使用 docker build -t ssh:centos_dockerfile . 进行构建
云计算
2020-02-07 13:06:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
今年的这个春节假期,对于所有中国人来说,都是一次非常难忘的记忆。受冠状病毒肺炎疫情的影响,各个企业的复工也面临着诸多挑战。在假期延长之后,在线办公的形式成为各个企业的共同选择。
在SaaS应用逐渐普及的当下,随着信息技术、5G、人工智能的高速发展和迅速普及,我们正在进入一个“云时代”。SaaS模式的云应用对于当下这个时间节点的中国企业来说,无疑不正是一个良好的选择。
目前,优秀的SaaS服务是在线订阅的基础上提供服务,免去了企业购买传统本地部署软件或者构建自己的本地部署软件的成本,轻松解决远程协同中的“沟通、流程、跨地域”这3大难题 。所有员工通过互联网、电脑、手机即可登录使用,同时它还允许客户根据自身需要扩大和缩减其支出、按需定制。
JEPaaS云平台,中国领先的企业数字化中台和SaaS快速开发领导者,是集低代码快速开发、实干型数字中台工具、SaaS快速开发运营管理、万物互联的物联网接口引擎这四大核心承载力的新一代企业级快速开发平台。JEPaaS的SaaS+PaaS平台,能提供字段级别的功能自定义,并且使用的通用编程语言,让众多开发者可以在不用学习新型编程语言的情况下,就可以在线进行二次开发。
JEPa aS平台功能架构
JEPaaS让更多的各行各业的企业成为其平台的客户,从而开发出基于他们平台的多种SaaS应用,而技术人员也不用再面对诸多的系统服务提供商难以抉择的困境。实现再统一云平台即可简单开发实现包括CRM、OA、HR、SCM、进销存管理等任何企业管理软件,而且不需要使用其他软件开发工具并立即在线运行。
JEPaaS专为SaaS而生
JEPaaS丰富的SaaS应用
JEPaaS的SaaS商城界面
JEPaaS的SaaS运营监控
肺炎疫情带来的影响正冲击着各行各业。一边是疫情防控战仍在持续,一边是大小企业陆续开启“云办公”模式。在这个全国“战疫”的特殊时期,各类数字化服务正发挥出更大的作用。JEPaaS云平台所打造的SaaS生态体系,正通过自身以及合作伙伴推出各类服务和解决方案,助力各地抗击疫情,也帮助企业恢复经营。
云计算
2020-02-07 11:12:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
这几天,全国中小学生经历了“过山车”一样的心情。 因为疫情的不断蔓延,1月27日,教育部下发通知,2020年春季学期延期开学。 随后,教育部又提出“利用网络平台,停课不停学”。 紧接着,阿里巴巴旗下的钉钉发起了“在家上课”计划,将“在线课堂”功能免费开放给全国大中小学使用,并覆盖广大农村地区的学校。同时,钉钉的群直播“连麦功能”也将免费开放。 这意味着,只需要通过电脑或者手机打开钉钉,老师便可以在家上课,同学们也可以在家听课。不仅如此,还可以通过“连麦”像课堂上一样互动,目前全国已经有上万所学校加入。 一天点了70万个赞 “真没想到,初六那天学生给我们点了70万个赞。”说这话的是金乡二中的英语老师陈方园。 大年初六,山东省金乡一中和金乡二中“网上开课”第一天,两校高三学生都通过钉钉直播上课。 每课90分钟,每天四节课,总计六小时的时间里,2600多名同学点赞70万次,平均每人点赞269次。 点赞之外,同学们还时不时地发出赞美:“上课效果真的很好”,“有一对一辅导的感觉”。 因为钉钉上的直播视频可以回放,同学们在二次学习后,还会和老师进行交流。 同一天晚上八点,浙江宁波的镇海仁爱中学也有两堂课在钉钉上同步进行。一堂是25分钟的英语课,讲解了“过去完成时”;一堂是10分钟的科学课,讲解了寒假作业中的难题。 通过手机钉钉直播讲课的老师 在这短短的时间里,120位学生点了15000次赞,不停为上课的老师“打call”。 “网络不仅没有减少课程效果,反而因为新奇和有趣,提升了同学们的参与度。很多人都‘连麦’回答了问题或者‘连麦’提问,整体的气氛很好。”负责智慧校园建设的教师王雪建说。 一拍即合的“在线课堂” 尽管钉钉直播课初六才开始,但各方的准备工作却早已进行。 新春之后,山东省金乡县两所高中的校领导们就已经对不断加重的疫情产生了担忧。他们商量着将“钉钉直播上课”作为备案。恰在此时,金乡县教育局下发“网络直播开课”的通知。 早在一年前,金乡县的130所学校、120家幼儿园和300多家培训机构就已经使用钉钉。但那时,钉钉更多用于“家校联系”、“一键传达”。如今,钉钉“在线课堂”免费开放解了燃眉之急。 大年初三,金乡二中的英语老师陈方园接到了“初六上课”的紧急通知。次日,她和其他几位老师“网络备课”一整天,完成学案,一来交给教育局打印发放,二来上传钉钉供学生自取。 几乎同一时间,浙江镇海的王雪建也接到了校长的电话,要求着手进行网上课堂建设的准备。 早在2019年9月,镇海仁爱学校就已经开始使用钉钉,王雪建正是这方面的负责人——钉钉提供教学平台,教育局、学校多方提前准备,这才有了同学们的“如期上课”。 500万学生在线上课 如果说初六是部分地区的学校尝试“网络上课”,是星星之火,那么现在钉钉的“在线课堂”已经燎原。 1月31日,山西省太原市第五中学、第十二中学迎来开年第一课,通过钉钉直播在家听课。 同日,浙江温州、海宁、丽水等城市的40余所学校也已经使用钉钉APP展开网络教学或在线通知等功能。 镇海中学高三任课老师王燕告诉记者,老师们正在紧急备课,以期尽快开展网络教学。 据了解,目前浙江、江苏、河南、山西、山东、湖北等20多个省份纷纷加入阿里巴巴钉钉“在家上课”计划,预计超过1万所大中小学、超过500万学生将通过钉钉直播的方式上课。 不少父母也会通过钉钉回看课程,了解孩子们的学习状态。一位山东家长告诉记者,自己平常也会辅导孩子,现在就和他一起在钉钉上学习,希望后面四个月的高考冲刺,可以更好地帮助他。 我们不知道疫情还会持续多久,但教育一定会持续进行,并且会最先迎来繁花盛开的春天。 上云就看云栖号,点此 查看更多 ! 本文为阿里云原创内容,未经允许不得转载。
云计算
2020-02-01 17:56:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
本文作者:oschina_2020
我们的中国文化,对“面子”看得特别重,所以你会发现身边到处都是高级 XXX,听着倍儿有面子,程序员也不例外。
但是你真要问每个人,你认为的高级 XXX 是什么样子的,估计每个人都有不同的回答。
我还记得在我刚开始从事编程工作的时候,对坐在边上不远的那位我心目中的高级程序员的印象是: 工作至少有 6、7 年以上,能写一个用起来很方便、看起来很牛逼、但是不太容易让初级人员看懂的框架。
前两天,我把这个问题丢到群里,大家给出的答案中,占比最高的是以下几个。 有 N 年以上编程经验(大部分都说 5 年以上) 有出版过技术图书 对某领域内对常用框架原理有了解,并且实际使用超过 2 年 可以随时随地快速写出常见的一些算法 至少封装过一个被全局使用的开发框架 写出来的代码,阅读起来很好理解 能带领其他人员成功完成项目
你看,这件事对大家来说就是常说的,“一千个人眼中有一千个哈姆雷特”。
不过这也正常,毕竟像初级、中级、高级这种高度抽象的词汇,想要得到一个可描述的定义与人交流,必然需要夹杂着个人的主观因素。
但是很多行业都在这么进行分类,自然有它的道理和好处。
我觉得 其中最大的一个好处恰好是“主观”的附属品——弹性 。
比如,我现在想招一位高级程序员,面试的时候不管是通过还是不通过,我都有理由来解释我对“高级”的定义。如此一来,我对陌生人的判断就有了更大的“弹性”。
这其实是面试官的一种权利,也是长期以来面试者总在面试中处于下峰的原因之一。
事物总是有两面性的,我们在对陌生人弹性的同时,间接地也对内部的人弹性了,会导致内部的一些人才培养出现问题。
比如,你觉得内部的高级程序员不够,希望能在外部招聘的同时,从内部也培养一些出来。但是此时,你又面临了需要定义什么是“高级”的问题。
如果没法定义一个能够达成共识的标准,又如何指导培养的方向呢?只能是一句空话。
长此以往会导致更严重的问题: 真正的高级程序员不够,只能让中级程序员顶上。顶替的时间长了,会让一些中级程序员误以为自己已经达到了高级水平 。
在我平时的面试中,这样的案例屡见不鲜,网上流传的工作 10 年 = 1 年重复 10 次的段子是真实存在的。
下面我来聊聊我对“什么是高级程序员”的个人看法,欢迎你和我一起探讨。
不管是什么行业,什么岗位,在这个高度分工协作的现代社会,所需的能力主要分为三个维度,我的理解大概是这样的: 专业能力:好奇心、敢于挑战困难、刻意养成好习惯、要求严格 连接能力:共同体意识、同理心、实事求是、接地气 领导能力:主人翁意识、沟通/谈判技巧、目的导向
先卖个关子,文章的最后我会将这三个维度组合起来,你会发现一片新的天地。
根据这三个维度的水平差异,我们对初级程序员、中级程序员、高级程序员做一个简要的描述。
初级程序员 - 知道有事要做
处在初级阶段的时候,我们的精力大多只会专注在专业能力的提升上。这个时候“领导能力”和“连接能力”是很弱的。
所以,这个时候哪怕你有强烈的好奇心也无法很好地表达出来,大多只能被动的接受工作安排。
在这个时期做事情需要依赖一些教程、文档,只能“依样画葫芦”,几乎不能在不借助外部信息的情况下解决之前从未遇到过的新问题,所以百度、Google 就成了他们唯一的选择。
你可以在你的身边观察一下,如果经常有以下这些场景出现,大多是初级程序员的表现。 很难提出正确的问题,大多会直接问别人这个功能应该怎么做。如果你清楚地向他解释,他就会完全按你说的去做,甚至你写的示例代码都会 copy 过去。因为在他们的世界里,只有编译成功和编译失败,任务完成和任务未完成。 经常犯错误,所以会预留过多“弹性时间”,以便有时间在到期日之前重做。所以总会抱怨“没时间”。 对与自己有工作交集的人员的职能没有认识。比如,对测试人员总是充满敌意的,因为他们发现了错误,“阻碍”了自己完成工作。 还没注意养成一些好习惯,比如习惯性地提炼重复代码、编写风格一致的代码、自测等等。
很遗憾,看似很初级的阶段,并不只是刚踏入工作的程序员所属,在实际工作中,也有不少工作多年的人还处在这个阶段。
中级程序员 - 知道如何做某事
对人群按照单一维度进行划分,大多数时候都是符合正态分布的,这里也不例外。中级程序员是我们身边最多的,包括那些不得不穿上高级程序员马甲的中级程序员。
在这个阶段,有些中级程序员开始具备了一定的“连接能力”,但并不是所有人,主要看是不是拥有了“共同体意识”。
在专业能力上,中级程序员已经明白了一定的“整体与局部”的概念,但仍然看不到整个“森林”,大多局限在某个模块、流程上。比如,他们会想“这是做敏捷的正确方式吗?”,但不会考虑“这对整个团队、整个公司会产生什么实际的影响?”。
他们开始注重代码质量,因为担心低质量的代码会影响他们视野中的“整体”。
但是对于质量的理解还是比较单一。比如,这个时候你会经常听到他们把“性能”挂在嘴边,在他们心目中“性能”的地位是至高无上的,总是想着你这个方案和我的方案哪个性能更好。
同样可以观察一下周围,中级的开发大多数会这样做事: 针对一个问题,可以提出多个方案,但是无法做出准确的决策。一旦更权威的人给出了他的选择,中级程序员就会不假思索地按照建议执行。 可以看出代码中的一些设计模式,但是自己写代码的时候除了单例和工厂,其它的几乎想不到。 在讨论一些时髦的框架和技术的时候总能聊上几句,但是追问这个框架或者技术有什么缺点,基本说不上来。甚至,草率地在项目中运用上这些时髦的框架和技术,最终导致线上问题频发,不得不让高级程序员来收拾残局。 能够对自己完成任务所需的时间有准确的评估,但是评估他人的时间不会因人而异,也会以自己作为标准来评估。 对与自己有工作交集的人员的职能有了一定的认识。比如,会主动寻求测试的配合,帮助自己交付更高质量的项目。
其实这个阶段是最危险的阶段,因为最可怕的不是无知,而是一知半解 。心理学中的邓宁-克鲁格效应(The Dunning-Kruger Effect)讲述的就是这个问题。
两位社会心理学家在 1999 年做的 4 项研究,证实了下面的这个曲线的存在。
在这种状态下,人最容易高估自己,这也是很多导致产生很多“假高级程序员”的原因所在。
高级程序员 - 知道必须做些什么
高级程序员在“专业能力”、“连接能力”与“领导能力”这三个维度都有所建树。因为他们不但可以把从 1 到 100 的事情做得很好,也有能力带领其它人完成 0 到 1 的事情。
根据我身边所接触的程序员群体来看,我所认为的高级程序员,他们明白没有什么是完美的,相反,问题、缺点和风险总是存在的。
他们的决策总是站在为了整体的“平衡”角度去考虑,而不是技术的酷炫或者外界流传的所谓“正确的”技术。
他们会更多地关心那些不显而易见的东西,如可维护性、可扩展性、易阅读、易调试等等。
高级程序员就好比社会中的成年人,他们踩过足够多的坑,也填过足够多的坑,已经认清了现实的残酷,寻求适合而不是完美。周到、务实、简单,是他们做事的时候强烈散发出的“味道”。
可以根据下面的这些场景来看看你身边有多少“有味道”的高级程序员? 与初级和中级程序员不同,他们抛出问题不是为了正确地做事,而是做正确的事。他们会询问为什么要这样做以及你想要实现什么。当你告诉他们目标是什么后,他们或许会通过暗示这种方式是错误的而另一种更好来做出一些修正;当然,更重要的是还会提供论据说服你。 因为提前明确了做事的目标,所以在动手做一件事的过程中,他会在关键细节思考有没有更好的方法,甚至是那些不在之前的讨论范围的新尝试。 他可以轻松地承认他不知道什么,并且向你请教。同时也可以轻松地向他人讲清楚他所知道的事情。 他们理解合作的人员的职能的作用,不但知道什么时候向谁寻求帮助,还知道自己如何更好地帮助他们。 困难的事交给他们很放心,因为他们擅长的不是某种技术,而是解决问题的能力。他们总能解决那些之前从未遇到过的新问题,哪怕它们很困难。
那么,怎么做有助于我们成为高级程序员呢?
1、关注技术之余还要关注业务
为什么把它放第一点,因为我觉得这点最重要,是其它项的基础,也最容易做到,但是很多程序员不愿意去做。
一定要搞清楚业务目标,不搞清楚不开工。相信我,只要是一位合格的 leader,一定会不厌其烦地和你说清楚的。
然后要习惯基于业务目标去分析可能会面临的技术挑战。比如,多少流量,涉及哪些用户角色和功能,复杂度有多大等等。
再带着下面的“不可能三角”去寻找合适的技术框架、解决方案。尽可能地寻求最优的平衡,而不是走极端。
如果拿捏不准,可以将多个方案各自的优缺点罗列出来,向 leader 寻求建议。
2、“设计”代码而不是“写”代码
一般人可能拿到需求,就开始写代码了,写着写着由于页面功能越来越多,感觉代码越来越复杂,自己都会觉得难以维护了。
虽说要做好设计离不开大量的实战经验的积累,但还是有些方法可以让塑造这个能力的过程更快一些。比如: 首先就是前面提到的第一点,多关注业务。不了解业务,你啥都设计不出来,或者把马设计成了驴…… 如果某个功能的开发/修改,以“天”为工时单位,一定要先画图。具体画什么图,可以参考我之前写的文章: 软件开发中会用到的图 。 搞明白每个设计模式的特点和适用场景,注意,不需要把代码怎么写背下来。只要你每次写代码之前扫一眼设计模式的列表,看看有没有适用的。如果有的话,再去“依样画葫芦”按照设计模式去实现,经过时间的积累,慢慢地,你真正掌握的设计模式就越来越多了。这有助于锻炼你的设计能力。
3、“接”需求之前会先“砍”需求
要做这点还得依赖于第一点,否则,你提出的“砍”需求建议大多是不会被采纳的。
很多人在听需求讲解的时候,思考的是,这个功能能不能实现、怎么实现、难不难。大多数的提问也是基于这个思路展开的。
可能也会提出“砍”需求的问题,但是理由大多是这个实现起来太麻烦了,这个没法实现之类。
其实只要你时刻保持着“做这个需求的目的是什么”这个问题去思考,“砍”需求会变成一件更容易成功,而且自然而然的事情。
4、解决一类问题而不是一个问题
很多人觉得,每天看到 bug 清完就万事大吉了,哪怕同一个问题在生产环境出现多次,最多也就说一句“不会吧,怎么又出问题了”。
这种对待问题的方式只会让你越来越忙,因为你的解决问题效率与投入的时间多少是成同比变化的。
我们要习惯于解决掉一个 bug 之后,想一下能否通过什么方式找到现有代码中的同类问题,并把它们处理掉。
甚至是考虑有没有什么办法能够一劳永逸地避免此类问题再次发生,比如封装一个 SDK 或者写一个组件,尽可能用一种低侵入的通用方式将问题扼杀在摇篮里。不但让自己轻松了,也造福了大家。
5、遵循 KISS 原则,写尽可能简单的代码
KISS 原则:保持简单,愚蠢(Keep it simple, stupid)。
不单单是程序员,任何化繁为简的能力才是一个人功力深厚的体现,没有之一 。
越简单,越接近本质。就好比,有的人要用长篇大论才能讲明白一件事,而有的人只要做一个形象的比喻你就懂了。
这个“简单”指的是整体的简单,而不是通过局部的复杂让另一个局部简单。比如,为了上层的使用更加傻瓜化,底层封装的代码错综复杂、晦涩难懂,这并不是真正的“简单”。
如果你自认为已经是一个中级或者高级程序员了,那么你回头去看看自己还是初级程序员那会写的代码,就会很容易发现一些显得冗余的代码。
第二点提到的——“设计代码而不是写代码”对做好这点有很大的帮助。
6、选择忍受某些问题
在人工智能还不能代替我们 coding 之前,我们永远要亲自面对无穷无尽的、这样那样的问题。
然而,任何事物都有两面性的,一个方案在解决一个老问题的同时,总会带来新的问题。所以,我们一定要意识到,忍受某些问题是必然的。
那些你现在看起来很傻逼的设计,可能就是当时的人做出的妥协。
所以,既然如此,你更应该考虑的是,当前的这个问题现在到底有没有必要解决?值不值得,为什么之前没去解决?它是不是你当前所有待解决问题列表中优先级最高的?
7、打造自己的“T型”专业技能
可能很多人都听过“T型人才”的概念,我们程序员在专业技能的打造上也适合用这种模型。
但是对于“先竖再横”还是“先横再竖”可能不同的人有不同的看法。
我的观点是, 大多数情况下,先竖再横。特别是某个技术、领域发展得越成熟,越应该如此 。
因为很多事物的本质是一样的,所以对某一个领域达到非常深入,洞察到一些本质的东西之后,对其它相邻的领域有触类旁通的效果。可以加速自己在“广度”上的扩展。
不过,“广度”也不是说蜻蜓点水,只知道最表象的“它是什么”。我认为比较合适的程度是,可以不用清楚某个技术具体的使用方式,但得知道它可以解决哪些问题,以及使用成本和潜在的风险,我将这些信息概括为 “它怎么样” 。
8、构建自驱动的“闭环”
很多人都知道闭环的概念,但是它的重要性和价值往往被低估。因为人总是短视的,“聚沙成塔”之类的方式总是不受待见。
常规的搭建一个闭环的过程大多是这样的。
这里所说的自驱动的“闭环”是这样的。
如何才能变成这样呢? 只要做一件事,尽可能多地对外输出自己的知识 。
举个我自己的例子,我在 2015 年那会在项目中开始引入领域驱动设计,并且不断地在内部进行分享它的好处,慢慢地越来越多的项目开始往这个方向走。
因为前期的不断分享,所以在组织内部,别人对我的人设多了一个“DDD专家”的标签,那么大家遇到有关 DDD 的问题就会来和我一起探讨。
越到后面,我已经不用自己主动去寻找这个领域的知识去学习了,因为接收到的外部反馈已经足够多了,它们能够倒逼我往前走。并且这些反馈都是实际的真实场景,此时的信息获取和学习自然能达到“学以致用”的效果。
说实话,有不少人并不是这么想的,他们想的恰恰相反:“为什么每个人都在问我问题!你自己去学习吧!”。
所以,当你遇到其他人来请教你的时候,如果恰巧这是你所关注的领域,那么应该去拥抱这个问题而不是排斥它。 因为你是团队里最权威的人,这是你构建自驱动“闭环”的好机会 。错过这一回,下一回不知道得等多久。
前面文章里说到,我会将“专业技能”、“连接外部的能力”、“领导力”三个维度组合起来给你看。就是下面这个样子。
你会发现这里面包含了程序员在进阶后的几个常见岗位。
可以对号入座一下:D
好了,我们总结一下。
这篇我先和你聊了一下在大家眼中高级程序员是什么样子,发现没有特别统一的标准,都是模糊的。这也体现在了几个现实的场景中,比如招聘高级程序员、培养高级程序员上。
其次,我对初级、中级、高级程序员的特点分别阐述了自己的观点。
然后,给出了一些帮助大家往高级程序员靠拢的实践思路。
希望对你有所启发。
最后,用Martin Fowler 的一句话作为结尾:“任何傻瓜都能写计算机能理解的代码,优秀的程序员编写人类能够理解的代码。” Any fool can write code that a computer can understand. Good programmers write code that humans can understand
Martin Fowler
希望看到这篇文章的每个程序员最终都能成为头发茂盛的码农:D
作者介绍
张帆(Zachary),7 年电商行业经验,5 年开发团队管理经验,4 年互联网架构经验,目前任职某垂直电商技术总监。专注大型系统架构与分布式系统,坚持用心打磨每一篇原创。个人公众号:跨界架构师(ID:Zachary_ZF)。
原文链接地址: https://developer.baidu.com/topic/show/290543
云计算
2020-01-20 17:14:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
随着以函数即服务(Function as a Service)为代表的无服务器计算(Serverless)的广泛使用,很多用户遇到了涉及多个函数的场景,需要组合多个函数来共同完成一个业务目标,这正是微服务“分而治之,合而用之”的精髓所在。本文以阿里云 函数计算 为例,试图全面介绍函数组合的常见模式和使用场景,希望有助于选择合适的解决方案。
虽然本文主要介绍的是函数组合,但是基本思想也可用于服务组合。
函数同步调用函数
在这种模式里,函数直接调用 InvokeFunction 同步 API 执行一个或者多个函数,等待被调用函数返回结果,然后继续执行。这是一个有些争议的模式,不使用同步调用通常有以下原因: 从费用的角度:由于函数计算按照函数实际执行时间收费,调用者在等待被调用函数返回前也会产生一定费用。 执行时长限制:由于函数最长执行10分钟,这就决定了调用的其它函数执行时间之和有限。 从容错的角度:被调用者出错会直接影响调用者,如果这个调用链很长,则这种错误会一直蔓延到最初的调用者,容错性较差。同时由于执行时长限制,调用者通常不容易针对错误做长时间重试。
上面的理由是在有些场景下成立的,但是微服务最经典最常见的组合方式就是同步调用,函数作为微服务的一种实现方式,这种同步调用的需求是不可回避的,在有些场景下采用同步调用模式是值得考虑的,这些场景包括: 调用者函数需要被调用函数执行结果做后续处理或者返回给客户端。 函数执行时间较短,最好在毫秒到秒级别。 调用者是无状态的,不需要针对被调用者的错误做复杂重试。
在这种模式里,调用者通常不需要做复杂的计算,主要时间花在调用函数和等待返回上,因此调用者函数可以设置较小的内存,以减少费用;调用者还可以根据业务需求缓存调用结果,减少对其它函数的调用,从而节约费用和增强容错性。
函数通过 API 网关调用函数
与函数直接调用函数不同,这里 被调用函数在 API 网关后面 ,使它看起来更像一个微服务。这种模式的限制和使用场景跟上面的直接调用模式类似,不同之处是 API 网关提供了一些额外的能力,比如认证和限流等。如果被调用者要根据某些业务信息对请求做处理,则使用 API 网关是一个好的选择。当然,使用 API 网关也带来了额外的延迟和费用,如果不需要使用 API 网关提供的能力,则让函数直接调用函数是一个更好的选择。
函数异步调用函数
在这种模式里,函数执行完自身业务逻辑后,调用 InvokeFunction 异步 API 通知其它函数执行并退出,它不关心被调用函数是否执行完成。函数计算的 InvokeFunction 异步 API 是通过队列实现的,异步请求首先被写入队列,然后有一个组件从队列里消费请求,执行相应函数。这种模式的优势和使用场景有: 对执行延迟不敏感的场景,适合削峰填谷,减轻对依赖服务的压力。函数计算会根据用户的资源使用情况动态的调整队列的处理速度,平滑请求对系统的压力,当然这种平滑也可能会导致一些请求被延缓执行。 调用者可以较容易的触发多个被调用者执行,实现 Fan-Out 模式。比如一个视频处理函数可以异步调用多个函数来分别将视频转换成不同格式。 由于每个函数所花时间都用在执行业务逻辑,而非等待其它函数返回上,没有产生不必要的费用。
这种模式有如下局限性: 调用者显然无法知道被调用者的执行结果。 被调用者之间最好是独立执行的,不需要相互协调,否则通过数据库来协调执行会增加复杂度。例如,上面提到的视频处理函数调用多个函数分别将视频转换成不同格式,如果需要在所有的视频转换完成后发邮件通知用户,这就需要等待所有的转换函数执行结束。这种 等待多个事件发生 的模式叫做 Fan-In,不适合通过这种异步调用模式实现。
基于消息主题的函数调用
与上面的模式需要指定被调用函数不同,基于事件触发模式让调用者只需要发布消息,不关心谁去消费消息。在消息服务(MNS)中,主题是发布消息的目的地,发布者可以通过 PublishMessage API 向主题发布消息, 主题的订阅者会接收到发布到主题上的消息, MNS 与函数计算服务的集成 让函数可以直接作为订阅者,简化了消息处理应用的开发和运维代价。
和上面的异步调用模式相比,基于消息主题的调用模式有以下优势: 进一步解耦函数调用。调用者无需知道被调用者的信息,只需要约定消息的格式。 得益于一个主题可以对应多个订阅者,更容易实现 Fan-Out 模式。发布者只需要发布一条消息就能触发多个函数。在上面的异步调用方式中,调用者仍需要显式调用一个或者多个函数来触发执行。 虽然主题模式本质上是消息服务 推送 消息给订阅者,不会考虑订阅者的承载能力,但是由于阿里云消息服务是通过 InvokeFunction 异步 API 调用函数,因此这种模式也具备基于消息队列模式的削峰填谷能力。
这种模式的局限性跟异步调用函数类似: 很难支持等待多个事件触发一个函数的场景(Fan-In)。 需要做一定工作才能跟踪多个函数执行状态。 很难限制单个函数的执行时间或者所有函数的总执行时间。 无法针对函数执行错误定义重试策略。
基于对象存储服务的函数调用
函数计算服务除了集成了消息主题以外,还集成了对象存储服务 (OSS),表格存储等其它事件源,这些事件源服务同样可以作为连接函数的渠道。比如,一个非函数应用对 OSS 对象操作(创建,删除等)后,通常需要其它渠道(比如消息服务)通知其它应用做后续处理,而由于 OSS 和函数计算的集成 ,只需简单配置,这些事件就可以直接传递给自定义函数,而不需要额外渠道再传递信息,简化了数据处理流水线的开发和运维代价。
基于日志库的函数调用
日志服务的 日志库 是一个流(Stream)存储,生产者写入数据到日志库,消费者读出数据并处理。 日志服务和函数计算的集成 ,使得消费者可以是函数,从而实现了通过流存储来协调函数调用。上面所有模式都是调用者显式或者隐式的触发一个或多个被调用函数执行,而这里生产者函数写入的数据不一定会立刻被消费者函数处理,日志服务会根据用户配置将多个数据批量推送给消费者函数。
这种模式有以下优势: 日志服务会针对每个分区(Shard)并行调用函数,如果数据吞吐较大,可以通过扩展分区来提高消费吞吐能力。 日志服务给函数推送的同一分区数据是串行的和严格保序的,在老的数据没有消费成功前不会推送新的数据。 日志服务会持续推送相同数据到函数直到函数消费数据后返回正常。
基于函数工作流的函数组合
函数工作流 (Function Flow,简称 FnF)是一个用来协调多个分布式任务执行的全托管 Serverless 云服务,简化了开发和运行业务流程所需要的任务协调、状态管理以及错误处理等繁琐工作,让用户更好的专注业务逻辑开发。可以说函数工作流是转为函数组合而生,有效的解决了上面几种异步组合模式的局限性。
上面的所有模式都是通过点对点的方式来组合函数,而函数工作流是一个集中的协调者,函数之前不再直接或者间接通信,所有的触发都是由函数工作流发起,不同函数的输入和输出是通过函数工作流来传递。因此,这种方式下的函数代码全是业务逻辑相关,没有上面模式里的发送主题消息,或者调用其它函数的逻辑,实现更加清晰。
函数工作流有以下优势: 服务编排能力:可以将流程逻辑与任务执行分开,支持多种控制原语,比如顺序执行多个函数,根据函数执行结果选择执行其它函数,让多个函数并行处理数据,或者让一个函数并行处理一组数据等,以及上面的 Fan-In 模式。函数工作流还内置了错误重试和捕获能力,节省了编写编排代码的时间。 支持长流程:无论是毫秒级还是长达一年的业务流程,FnF 都可以跟踪整个流程,确保流程执行完成。 流程状态管理:FnF 会管理流程执行中的所有状态,包括跟踪它所处的执行步骤,以及存储在步骤之间的输入输出。您无需自己管理流程状态,也不必将复杂的状态管理构建到任务中。 协调分布式组件:FnF 能够协调运行在不同架构,不同网络,不同语言实现的分布式应用。无论是私有云、专有云的应用想要平滑过渡到混合云、公共云,还是单体架构的应用想要演进到微服务架构,FnF 都能在其中发挥协调作用。函数工作流通过集成消息队列,让任何可以访问消息队列的应用也可以作为流程一部分,相互协作,共同完成业务目标。 可视化监控:FnF 提供了可视化界面来协助定义流程和查看执行状态,方便您快速识别故障位置,并快速排除故障问题。 运维全托管和按需付费:FnF 让您从基础设施维护中解放出来,提供了安全的、高可用的、高容错的弹性服务。用户只需支付步骤转换费用,不使用不产生费用。
函数工作流目前还不支持同步调用方式,如果您有同步调用需求,欢迎联系我们(见文章最后钉钉客户群)。
总结
总的来说,上面的组合模式可以分为同步和异步两种,在场景适合的情况下优先选择异步模式,享受异步模式带来的松耦合,高容错等特性,否则使用同步模式。在异步模式中,如果需要编写复杂的组合逻辑,支持可靠的重试,把控整个流程,则推荐使用函数工作流,否则使用消息主题或者其它事件源服务来组合函数。
上面的模式也不是割裂的,它们在有些场景下可以搭配使用,比如有时候基于对象存储服务的调用需要触发多个函数,这就可以 结合使用 OSS 的事件触发和函数工作流 ;又比如 函数工作流通过消息队列将任务发送给更广泛的消费者 ,触达函数计算无法触达的地方。
最后,欢迎加入函数工作流和函数计算客户群。
“ 阿里巴巴云原生 关注微服务、Serverless、容器、Service Mesh 等技术领域、聚焦云原生流行技术趋势、云原生大规模的落地实践,做最懂云原生开发者的技术圈。”
云计算
2020-01-19 15:13:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
Helm是Kubernetes的一个软件包管理器。两个月前,它发布了第三个主要版本,Helm 3。在这一新版本中,有许多重大变化。本文将介绍我认为最关键的5个方面。
1、 移除了Tiller
Helm最终移除了其服务器端组件,Tiller。现在,它完全没有代理。Tiller之前是一个运行在Kubernetes上的小型应用程序,它用于监听Helm命令并处理设置Kubernetes资源的实际工作。
这是Helm3中最重大的更改。为什么Tiller的移除备受关注呢?首先,Helm应该是一种在Kubernetes配置上的模板机制。那么,为什么需要在服务器上运行某些代理呢?
Tiller本身也存在一些问题,因为它需要集群管理员的ClusterRole才能创建。因此,假设你要在Google Cloud Platform中启动的Kubernetes集群上运行Helm应用程序。首先,你需要启动一个新的GKE集群,然后使用helm init初始化Helm,然后…发现它失败了。这种情况之所以会发生是因为,在默认状态下,你没有给你的kubectl上下文分配管理员权限。现在你了解到了这一点,开始搜索为分配管理员权限的magic命令。这一系列操作下来,也许你已经开始怀疑Helm是否真的是一个不错的选择。
此外,由于Tiller使用的访问权限与你在kubectl上下文中配置的访问权限不同。因此,你也许可以使用Helm创建应用程序,但你可能无法使用kubectl创建该程序。这一情况如果没排查出来,看起来感觉像是安全漏洞。
幸运的是,现在Tiller已经被完全移除,Helm现在是一个客户端工具。这一更改会导致以下结果: Helm使用与kubectl上下文相同的访问权限 你无需再使用helm init来初始化Helm Release Name 位于命名空间中
Helm 3一直保持不变的是:它应该只是一个在Kubernetes API上执行操作的工具。如此,如果你可以使用纯粹的kubectl命令执行某项操作,那么也可以使用helm执行该操作。
2、 分布式仓库以及Helm Hub
Helm命令可以从远程仓库安装Chart。在Helm 3之前,它通常使用预定义的中心仓库,但你也能够添加其他仓库。但是从现在开始,Helm将其仓库模型从集中式迁移到分布式。这意味着两个重要的改变: 预定义的中心仓库被移除 Helm Hub(一个发现分布式chart仓库的平台)被添加到helm search
为了能够更好地理解这一改变,我给你们一个示例。在Helm 3之前,如果你想要安装一个Hazelcast集群,你需要执行以下命令: $ helm2 install --name my-release stable/hazelcast
现在,这个命令不起作用了。你需要先添加远程仓库才能进行安装。这是因为这里不再存在一个预定义中心仓库。要安装Hazelcast集群,你首先需要添加其仓库然后安装chart: $ helm3 repo add hazelcast https://hazelcast.github.io/charts/ $ helm3 repo update $ helm3 install my-release hazelcast/hazelcast
好消息是现在Helm 命令可以直接在Helm Hub中寻找Chart。例如,如果你想知道在哪个仓库中可以找到Hazelcast,你只需执行以下命令即可: $ helm3 search hub hazelcast
以上命令列出在Helm Hub中所有分布式仓库中名称中包含“hazelcast”的Chart。
现在,我来问你一个问题。移除掉中心仓库是进步还是退步?这有两种观点。第一种是chart维护者的观点。例如,我们维护Hazelcast Helm Chart,而Chart中的每个更改都需要我们将其传播到中心仓库中。这项额外的工作使得中心仓库中的许多Helm Chart没有得到很好地维护。这一情况与我们在Ubuntu/Debian包仓库中所经历的很相似。你可以使用默认仓库,但它常常只有旧的软件包版本。
第二种观点来自Chart的使用者。对于他们来说,虽然现在安装一个chart比之前稍微困难了一些,但另一方面,他们能够从主要的仓库中安装到最新的chart。
3、 JSON Schema 验证
从Helm 3开始,chart维护者可以为输入值定义JSON Schema。这一功能的完善十分重要,因为迄今为止你可以在values.yaml中放入任何你所需的内容,但是安装的最终结果可能不正确或出现一些难以理解的错误消息。
例如,你在port参数中输入字符串而不是数字。那么你会收到以下错误: $ helm2 install --name my-release --set service.port=string-name hazelcast/hazelcast Error: release my-release failed: Service in version "v1" cannot be handled as a Service: v1.Service.Spec: v1.ServiceSpec.Ports: []v1.ServicePort: v1.ServicePort.Port: readUint32: unexpected character: �, error found in #10 byte of ...|","port":"wrong-name|..., bigger context ...|fault"},"spec":{"ports":[{"name":"hzport","port":"wrong-name","protocol": "TCP","targetPort":"hazelca|...
你不得不承认这个问题难以分析和理解。
此外,Helm 3默认添加了针对Kubernetes对象的OpenAPI验证,这意味着发送到Kubernetes API的请求将会被检查是否正确。这对于Chart维护者来说,是一项重大利好。
4、 Helm 测试
Helm测试是一个小小的优化。尽管微小,但它也许实际上鼓励了维护者来写Helm测试以及用户在安装完每个chart之后执行helm test命令。在Helm 3之前,进行测试多少都显得有些奇怪:
1、 此前测试作为Pod执行(好像需要一直运行);现在你可以将其定义为Job。
2、 测试Pod不会自动被移除(除非你使用magic flag –cleanup ),所以默认状态下,没有任何技巧,对于既定的版本你不能多次执行helm test。但幸运的是,现在可以自动删除测试资源(Pod、Job)。
当然旧的测试版本也并非不能使用,只需要使用Pod并始终记得执行 helm test –cleanup 。但也不得不承认,这一改进有助于提升测试体验。
5、 命令行语法
最后一点是,Helm命令语法有所改变。从积极的一面来看,我认为所有的改变都是为了让体验更好;从消极的方面看,这一语法不与之前的版本兼容。因此,现在编写有关如何使用Helm安装东西的步骤时,需要明确指出所使用的命令是用于Helm 2还是用于Helm 3。
举个例子,从 helm install 开始说起。现在版本名称已经成为必填参数,尽管在Helm 2中你可以忽略它,名称也能够自动生成。如果在Helm3中要达成相同的效果,你需要添加参数 --generate-name 。所以,使用Helm 2进行标准的安装应该如下: $ helm2 install --name my-release hazelcast/hazelcast
在Helm 3中,需要执行以下命令: $ helm3 install my-release hazelcast/hazelcast
还有另一个比较好的改变是,删除Helm版本后,无需添加—purge。简单地输入命令 helm uninstall 即可删除所有相关的资源。
还有一些其他改变,如一些命令被重命名(不过使用旧的名称作为别名),有一些命令则被删除(如 helm init )。如果你还想了解更多关于Helm 命令语法更改的信息,请参考官方文档:
https://helm.sh/docs/faq/#cli-command-renames
结 论
Helm 3的发布,使得这一工具迈向一个新的阶段。作为用户,我十分喜欢Helm现在只是一个单纯的客户端工具。作为Chart维护者,Helm Hub以及分布式仓库的方法深得我心。我希望能在未来看到更多更有意思的改变。
如果你想了解Helm 3中的所有变化,请查看官方文档:
https://helm.sh/docs/faq/#changes-since-helm-2 原文链接:
https://dzone.com/articles/helm-3-top-five-improvements
云计算
2020-01-13 10:34:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
作者 | 匡大虎  阿里巴巴技术专家 导读 :如何解决多租户集群的安全隔离问题是企业上云的一个关键问题,本文主要介绍 Kubernetes 多租户集群的基本概念和常见应用形态,以及在企业内部共享集群的业务场景下,基于 Kubernetes 原生和 ACK 集群现有安全管理能力快速实现多租户集群的相关方案。
什么是多租户集群?
这里首先介绍一下"租户",租户的概念不止局限于集群的用户,它可以包含为一组计算,网络,存储等资源组成的工作负载集合。而在多租户集群中,需要在一个集群范围内(未来可能会是多集群)对不同的租户提供尽可能的安全隔离,以最大程度的避免恶意租户对其他租户的攻击,同时需要保证租户之间公平地分配共享集群资源。
在隔离的安全程度上,我们可以将其分为软隔离 (Soft Multi-tenancy) 和硬隔离 (Hard Multi-tenancy) 两种。 其中软隔离更多的是面向企业内部的多租需求,该形态下默认不存在恶意租户,隔离的目的是为了内部团队间的业务保护和对可能的安全攻击进行防护; 而硬隔离面向的更多是对外提供服务的服务供应商,由于该业务形态下无法保证不同租户中业务使用者的安全背景,我们默认认为租户之间以及租户与 K8s 系统之间是存在互相攻击的可能,因此这里也需要更严格的隔离作为安全保障。
关于多租户的不同应用场景,在下节会有更细致的介绍。
多租户应用场景
下面介绍一下典型的两种企业多租户应用场景和不同的隔离需求:
企业内部共享集群的多租户
该场景下集群的所有用户均来自企业内部,这也是当前很多 K8s 集群客户的使用模式,因为服务使用者身份的可控性,相对来说这种业务形态的安全风险是相对可控的,毕竟老板可以直接裁掉不怀好意的员工:)根据企业内部人员结构的复杂程度,我们可以通过命名空间对不同部门或团队进行资源的逻辑隔离,同时定义以下几种角色的业务人员: 集群管理员:具有集群的管理能力(扩缩容、添加节点等操作);负责为租户管理员创建和分配命名空间;负责各类策略(RAM/RBAC/networkpolicy/quota...)的 CRUD; 租户管理员:至少具有集群的 RAM 只读权限;管理租户内相关人员的 RBAC 配置; 租户内用户:在租户对应命名空间内使用权限范围内的 K8s 资源。
在建立了基于用户角色的访问控制基础上,我们还需要保证命名空间之间的网络隔离,在不同的命名空间之间只能够允许白名单范围内的跨租户应用请求。
另外,对于业务安全等级要求较高的应用场景,我们需要限制应用容器的内核能力,可以配合 seccomp / AppArmor / SELinux 等策略工具达到限制容器运行时刻 capabilities 的目的。
当然 Kubernetes 现有的命名空间单层逻辑隔离还不足以满足一部分大型企业应用复杂业务模型对隔离需求,我们可以关注  Virtual Cluster ,它通过抽象出更高级别的租户资源模型来实现更精细化的多租管理,以此弥补原生命名空间能力上的不足。
SaaS & KaaS 服务模型下的多租户
在 SaaS 多租场景下, Kubernetes 集群中的租户对应为 SaaS 平台中各服务应用实例和 SaaS 自身控制平面,该场景下可以将平台各服务应用实例划分到彼此不同的命名空间中。而服务的最终用户是无法与 Kubernetes 的控制平面组件进行交互,这些最终用户能够看到和使用的是 SaaS 自身控制台,他们通过上层定制化的 SaaS 控制平面使用服务或部署业务(如下左图所示)。
例如,某博客平台部署在多租户集群上运行。在该场景下,租户是每个客户的博客实例和平台自己的控制平面。平台的控制平面和每个托管博客都将在不同的命名空间中运行。客户将通过平台的界面来创建和删除博客、更新博客软件版本,但无法了解集群的运作方式。
KaaS 多租场景常见于云服务提供商,该场景下业务平台的服务直接通过 Kubernetes 控制平面暴露给不同租户下的用户,最终用户可以使用 K8s 原生 API 或者服务提供商基于 CRDs/controllers 扩展出的接口。出于隔离的最基本需求,这里不同租户也需要通过命名空间进行访问上的逻辑隔离,同时保证不同租户间网络和资源配额上的隔离。
与企业内部共享集群不同,这里的最终用户均来自非受信域,他们当中不可避免的存在恶意租户在服务平台上执行恶意代码,因此对于 SaaS/KaaS 服务模型下的多租户集群,我们需要更高标准的安全隔离,而 Kubernetes 现有原生能力还不足以满足安全上的需求,为此我们需要如安全容器这样在容器运行时刻内核级别的隔离来强化该业务形态下的租户安全。
实施多租户架构
在规划和实施多租户集群时,我们首先可以利用的是 Kubernetes 自身的资源隔离层,包括集群本身、命名空间、节点、pod 和容器均是不同层次的资源隔离模型。当不同租户的应用负载能够共享相同的资源模型时,就会存在彼此之间的安全隐患。为此,我们需要在实施多租时控制每个租户能够访问到的资源域,同时在资源调度层面尽可能的保证处理敏感信息的容器运行在相对独立的资源节点内;如果出于资源开销的角度,当有来自不同租户的负载共享同一个资源域时,可以通过运行时刻的安全和资源调度控制策略减少跨租户攻击的风险。
虽然 Kubernetes 现有安全和调度能力还不足以完全安全地实施多租隔离,但是在如企业内部共享集群这样的应用场景下,通过命名空间完成租户间资源域的隔离,同时通过 RBAC、PodSecurityPolicy、NetworkPolicy 等策略模型控制租户对资源访问范围和能力的限制,以及现有资源调度能力的结合,已经可以提供相当的安全隔离能力。而对于 SaaS、KaaS 这样的服务平台形态,我们可以通过阿里云容器服务近期推出的 安全沙箱容器 来实现容器内核级别的隔离,能够最大程度的避免恶意租户通过逃逸手段的跨租户攻击。
本节重点关注基于 Kubernetes 原生安全能力的多租户实践。
访问控制
AuthN & AuthZ & Admission
ACK 集群的授权分为 RAM 授权和 RBAC 授权两个步骤,其中 RAM 授权作用于集群管理接口的访问控制,包括对集群的 CRUD 权限(如集群可见性、扩缩容、添加节点等操作),而 RBAC 授权用于集群内部 Kubernetes 资源模型的访问控制,可以做到指定资源在命名空间粒度的细化授权。
ACK 授权管理为租户内用户提供了不同级别的预置角色模板,同时支持绑定多个用户自定义的集群角色,此外支持对批量用户的授权。如需详细了解 ACK 上集群相关访问控制授权,请参阅 相关帮助文档 。
NetworkPolicy
NetworkPolicy 可以控制不同租户业务 pod 之间的网络流量,另外可以通过白名单的方式打开跨租户之间的业务访问限制。
您可以在使用了 Terway 网络插件的容器服务集群上配置 NetworkPolicy, 这里 可以获得一些策略配置的示例。
PodSecurityPolicy
PSP 是 K8s 原生的集群维度的资源模型,它可以在apiserver中pod创建请求的 admission 阶段校验其运行时刻行为是否满足对应 PSP 策略的约束,比如检查 pod 是否使用了 host 的网络、文件系统、指定端口、PID namespace 等,同时可以限制租户内的用户开启特权(privileged)容器,限制挂盘类型,强制只读挂载等能力;不仅如此,PSP 还可以基于绑定的策略给 pod 添加对应的 SecurityContext,包括容器运行时刻的 uid,gid 和添加或删除的内核 capabilities 等多种设置。
关于如何开启 PSP admission 和相关策略及权限绑定的使用,可以参阅 这里 。
OPA
OPA(Open Policy Agent)是一种功能强大的策略引擎,支持解耦式的 policy decisions 服务并且社区已经有了相对成熟的与 Kubernetes 的 集成方案 。当现有 RBAC 在命名空间粒度的隔离不能够满足企业应用复杂的安全需求时,可以通过 OPA 提供 object 模型级别的细粒度访问策略控制。
同时 OPA 支持七层的 NetworkPolicy 策略定义及基于 labels/annotation 的跨命名空间访问控制,可以作为 K8s 原生 NetworkPolicy 的有效增强。
资源调度相关
Resource Quotas & Limit Range
在多租户场景下,不同团队或部门共享集群资源,难免会有资源竞争的情况发生,为此我们需要对每个租户的资源使用配额做出限制。其中 ResourceQuota 用于限制租户对应命名空间下所有 pod 占用的总资源 request 和 limit,LimitRange 用来设置租户对应命名空间中部署 pod 的默认资源 request 和 limit 值。另外我们还可以对租户的存储资源配额和对象数量配额进行限制。
关于资源配额的详细指导可以参见 这里 。
Pod Priority/Preemption
从 1.14 版本开始 pod 的优先级和抢占已经从 beta 成为稳定特性,其中 pod priority 标识了 pod 在 pending 状态的调度队列中等待的优先级;而当节点资源不足等原因造成高优先的 pod 无法被调度时,scheduler 会尝试驱逐低优先级的 pod 来保证高优先级 pod 可以被调度部署。
在多租户场景下,可以通过优先级和抢占设置确保租户内重要业务应用的可用性;同时 pod priority 可以和 ResouceQuota 配合使用,完成租户在指定优先级下有多少配额的限制。
Dedicated Nodes
注意:恶意租户可以规避由节点污点和容忍机制强制执行的策略。以下说明仅用于企业内部受信任租户集群,或租户无法直接访问 Kubernetes 控制平面的集群。
通过对集群中的某些节点添加污点,可以将这些节点用于指定几个租户专门使用。在多租户场景下,例如集群中的 GPU 节点可以通过污点的方式保留给业务应用中需要使用到 GPU 的服务团队使用。集群管理员可以通过如 effect: "NoSchedule" 这样的标签给节点添加污点,同时只有配置了相应容忍设置的 pod 可以被调度到该节点上。
当然恶意租户可以同样通过给自身 pod 添加同样的容忍配置来访问该节点,因此仅使用节点污点和容忍机制还无法在非受信的多租集群上保证目标节点的独占性。
关于如何使用节点污点机制来控制调度,请参阅 这里 。
敏感信息保护
secrets encryption at REST
在多租户集群中不同租户用户共享同一套 etcd 存储,在最终用户可以访问 Kubernetes 控制平面的场景下,我们需要保护 secrets 中的数据,避免在访问控制策略配置不当情况下的敏感信息泄露。为此可以参考 K8s 原生的 secret 加密能力,请参阅 这里 。
ACK 也提供了基于阿里云 KMS 服务的 secrets 加密开源解决方案,可以参阅 这里 。
总结
在实施多租户架构时首先需要确定对应的应用场景,包括判断租户内用户和应用负载的可信程度以及对应的安全隔离程度。在此基础上以下几点是安全隔离的基本需求: 开启 Kubernetes 集群的默认安全配置:开启 RBAC 鉴权并实现基于namespace的软隔离;开启 secrets encryption 能力,增强敏感信息保护;基于 CIS Kubernetes benchmarks 进行相应的安全配置; 开启 NodeRestriction, AlwaysPullImages, PodSecurityPolicy 等相关 admission controllers; 通过 PSP 限制 pod 部署的特权模式,同时控制其运行时刻 SecurityContext; 配置 NetworkPolicy; 使用Resource Quota & Limit Range限制租户的资源使用配额; 在应用运行时刻遵循权限的最小化原则,尽可能缩小pod内容器的系统权限; Log everything; 对接监控系统,实现容器应用维度的监控。
而对于如 SaaS、KaaS 等服务模型下,或者我们无法保证租户内用户的可信程度时,我们需要采取一些更强有力的隔离手段,比如: 使用如 OPA 等动态策略引擎进行网络或 Object 级别的细粒度访问控制; 使用安全容器实现容器运行时刻内核级别的安全隔离; 完备的监控、日志、存储等服务的多租隔离方案。
注意: 文中图片来源 。 “ 阿里巴巴云原生 关注微服务、Serverless、容器、Service Mesh 等技术领域、聚焦云原生流行技术趋势、云原生大规模的落地实践,做最懂云原生开发者的技术圈。”
云计算
2020-01-10 21:40:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
在Heketi的glusterd容器服务,使用systemctl探针来检测glusterfs服务是否可用,发现总是出现失败问题。
经查,在Ubuntu 18.04 上 systemctl status glusterd.service 运行时输出信息不是K8s livenessProbe希望的,导致检测器超时挂起了。 使用 systemctl status glusterd.service并不能检测到服务的真实状态 ,会挂起、超时,返回错误状态码。
使用下面的方式,可以正确检测service的真实状态: systemctl is-active --quiet glusterd.service; echo $?;
或者(类似于): systemctl is-active sshd >/dev/null 2>&1 && echo 0 || echo 1
输出: 正常时 0; 非正常时为错误码。 如下所示: livenessProbe: exec: command: - /bin/bash - -c - systemctl is-active --quiet glusterd.service; echo $?; failureThreshold: 3 initialDelaySeconds: 60 periodSeconds: 10 successThreshold: 1 timeoutSeconds: 3 readinessProbe: exec: command: - /bin/bash - -c - systemctl is-active --quiet glusterd.service; echo $?;
修改后的k8s yaml文件如下: apiVersion: apps/v1 kind: DaemonSet metadata: name: glusterfs-daemon namespace: gluster labels: k8s-app: glusterfs-node spec: selector: matchLabels: name: glusterfs-daemon template: metadata: labels: name: glusterfs-daemon spec: tolerations: - key: node-role.kubernetes.io/master effect: NoSchedule containers: - image: gluster/gluster-centos:latest imagePullPolicy: IfNotPresent name: glusterfs livenessProbe: exec: command: - /bin/bash - -c - systemctl is-active --quiet glusterd.service; echo $?; failureThreshold: 3 initialDelaySeconds: 60 periodSeconds: 10 successThreshold: 1 timeoutSeconds: 3 readinessProbe: exec: command: - /bin/bash - -c - systemctl is-active --quiet glusterd.service; echo $?; failureThreshold: 3 initialDelaySeconds: 60 periodSeconds: 10 successThreshold: 1 timeoutSeconds: 3 resources: {} securityContext: capabilities: {} privileged: true terminationMessagePath: /dev/termination-log terminationMessagePolicy: File volumeMounts: - mountPath: /var/lib/heketi name: glusterfs-heketi - mountPath: /run name: glusterfs-run - mountPath: /run/lvm name: glusterfs-lvm - mountPath: /etc/glusterfs name: glusterfs-etc - mountPath: /var/log/glusterfs name: glusterfs-logs - mountPath: /var/lib/glusterd name: glusterfs-config - mountPath: /dev name: glusterfs-dev - mountPath: /sys/fs/cgroup name: glusterfs-cgroup dnsPolicy: ClusterFirst hostNetwork: true restartPolicy: Always schedulerName: default-scheduler securityContext: {} terminationGracePeriodSeconds: 30 volumes: - hostPath: path: /var/lib/heketi type: "" name: glusterfs-heketi - emptyDir: {} name: glusterfs-run - hostPath: path: /run/lvm type: "" name: glusterfs-lvm - hostPath: path: /etc/glusterfs type: "" name: glusterfs-etc - hostPath: path: /var/log/glusterfs type: "" name: glusterfs-logs - hostPath: path: /var/lib/glusterd type: "" name: glusterfs-config - hostPath: path: /dev type: "" name: glusterfs-dev - hostPath: path: /sys/fs/cgroup type: "" name: glusterfs-cgroup
可能在不同的Linux版本上,systemd的版本不同,参数也可能不一样,输入systemctl help来获取当前版本的帮助。
云计算
2020-01-07 22:22:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
ceph的MDS是cephFS文件存储服务的元数据服务。
当创建cephfs后便会有ceph-mds服务进行管理。默认情况下ceph会分配一个mds服务管理cephfs,即使已经创建多个mds服务,如下: [root@ceph-admin my-cluster]# ceph-deploy mds create ceph-node01 ceph-node02 ....... [root@ceph-admin ~]# ceph -s cluster: id: 06dc2b9b-0132-44d2-8a1c-c53d765dca5d health: HEALTH_OK services: mon: 2 daemons, quorum ceph-admin,ceph-node01 mgr: ceph-admin(active) mds: mytest-fs-1/1/1 up {0=ceph-admin=up:active}, 2 up:standby osd: 3 osds: 3 up, 3 in rgw: 2 daemons active data: pools: 8 pools, 64 pgs objects: 299 objects, 137 MiB usage: 3.4 GiB used, 297 GiB / 300 GiB avail pgs: 64 active+clean
此时mds中只有ceph-admin处于active状态,其余处于standby状态。
standby已近是一种灾备状态,但事实上切换速度比较缓慢,对于实际业务系统必定会造成影响。
测试情况如下: [root@ceph-admin my-cluster]# killall ceph-mds [root@ceph-admin my-cluster]# ceph -s cluster: id: 06dc2b9b-0132-44d2-8a1c-c53d765dca5d health: HEALTH_WARN 1 filesystem is degraded services: mon: 2 daemons, quorum ceph-admin,ceph-node01 mgr: ceph-admin(active) mds: mytest-fs-1/1/1 up {0=ceph-node02=up:rejoin}, 1 up:standby osd: 3 osds: 3 up, 3 in rgw: 2 daemons active data: pools: 8 pools, 64 pgs objects: 299 objects, 137 MiB usage: 3.4 GiB used, 297 GiB / 300 GiB avail pgs: 64 active+clean [root@ceph-admin my-cluster]# ceph -s cluster: id: 06dc2b9b-0132-44d2-8a1c-c53d765dca5d health: HEALTH_OK services: mon: 2 daemons, quorum ceph-admin,ceph-node01 mgr: ceph-admin(active) mds: mytest-fs-1/1/1 up {0=ceph-node02=up:active}, 1 up:standby osd: 3 osds: 3 up, 3 in rgw: 2 daemons active data: pools: 8 pools, 64 pgs objects: 299 objects, 137 MiB usage: 3.4 GiB used, 297 GiB / 300 GiB avail pgs: 64 active+clean io: client: 20 KiB/s rd, 3 op/s rd, 0 op/s wr
系统在active的mds被kill之后,standby的mds在经过rejoin状态后才变成了active,大约经过3-5s。而在生产环境中由于元数据的数据量更庞大,往往会更漫长。
而要让mds更快切换,需要将我们的mds服务切换至 standby_replay 状态,官方对于此状态的说明如下:
The MDS is following the journal of another up:active MDS. Should the active MDS fail, having a standby MDS in replay mode is desirable as the MDS is replaying the live journal and will more quickly takeover. A downside to having standby replay MDSs is that they are not available to takeover for any other MDS that fails, only the MDS they follow.
事实上就是standby_replay会实时根据active的mds元数据日志进行同步更新,这样就能加快切换的速率,那么如何让mds运行在standby_replay状态? [root@ceph-node01 ~]# ps aufx|grep mds root 700547 0.0 0.0 112704 976 pts/1 S+ 13:45 0:00 \_ grep --color=auto mds ceph 690340 0.0 0.5 451944 22988 ? Ssl 10:09 0:03 /usr/bin/ceph-mds -f --cluster ceph --id ceph-node01 --setuser ceph --setgroup ceph [root@ceph-node01 ~]# killall ceph-mds [root@ceph-node01 ~]# /usr/bin/ceph-mds -f --cluster ceph --id ceph-node01 --setuser ceph --setgroup ceph --hot-standby 0 starting mds.ceph-node01 at -
我们手动关闭了ceph-mds服务并且添加了--hot-standby 参数重新启动了mds服务。
接下来看到,ceph-node02的mds已经处于standby-replay状态: [root@ceph-admin my-cluster]# ceph -s cluster: id: 06dc2b9b-0132-44d2-8a1c-c53d765dca5d health: HEALTH_OK services: mon: 2 daemons, quorum ceph-admin,ceph-node01 mgr: ceph-admin(active) mds: mytest-fs-1/1/1 up {0=ceph-admin=up:active}, 1 up:standby-replay, 1 up:standby osd: 3 osds: 3 up, 3 in rgw: 2 daemons active data: pools: 8 pools, 64 pgs objects: 299 objects, 137 MiB usage: 3.4 GiB used, 297 GiB / 300 GiB avail pgs: 64 active+clean io: client: 851 B/s rd, 1 op/s rd, 0 op/s wr
当然或者可以ceph-mds的启动脚本来让服务启动时自动处于standby-replay状态
云计算
2020-01-03 14:01:01
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
2019年已经过去,小编为大家整理了这一年以来云加社区发布的 200多篇腾讯干货,点击文章标题即可跳转到原文,请速速收藏哦~
看腾讯技术 :
腾讯成本优化黑科技: 整机CPU利用率最高提升至90% ;
腾讯科技升级1000天: 团战、登月与烟囱革命 ;
“看一看”推荐模型揭秘!微信团队提出实时Look-alike算法,解决推荐系统多样性问题 ;
10亿级存储挑战!看一看、微信广告、微信支付、小程序都在用的存储系统究竟是怎么扛住的?!
微信扫物上线,全面揭秘扫一扫背后的识物技术!
微信搜一搜正在用的新一代海量数据搜索引擎 TurboSearch 是什么来头?
微信读书怎么给你做推荐的?
浅谈微视推荐系统中的特征工程 ;
一直陪伴你成长的 QQ 相册后台长什么样?
一支笔撑起黑科技课堂? 背后的技术秘密原来是这样的 ;
服务15亿+游戏用户数据的腾讯iData分析中心进化之路: 计算引擎基础能力构建 ;
服务15亿+游戏用户数据的腾讯iData分析中心进化之路: 为什么要用Spark? ;
旧的不去、新的不来: 腾讯iData分析中心的进化之路 ;
2019腾讯区块链白皮书 | 附完整版下载 ;
IPv4 地址已耗尽,IPv6 涅槃重生 | 腾讯云IPv6改造综 ;
述 腾讯云IPv6私有网络及负载均衡最佳实践指南 。

看腾讯上云:
冲上云霄! 腾讯海量业务上云实践 ;
基于腾讯云TKE的大规模强化学习实践 ;
聚焦技术和实践,腾讯云全面揭秘基础设施和大数据演进之路 ;
如何利用Kubernetes集群提升资源利用率?
腾讯游戏营销活动在腾讯云K8S上的实践 ;
腾讯自研业务上云: 优化Kubernetes集群负载的技术方案探讨 ;
重磅! 腾讯云首次披露自研业务上云历程 ;
迁移1500TB视频! 腾讯课堂音视频上云变革之路 ;
细节披露:腾讯课堂是如何把海量视频资源搬上云端的?
硬核实战 | 腾讯在线教育部上云实践和架构演进思考 。

看腾讯开源:
从用户成为“股东”--在Apache基金会的2600天 ;
覆盖4.6亿+设备量!腾讯开源的Hardcoder框架是如何提升Android APP性能的?
首次! 腾讯全面公开整体开源路线图 ;
腾讯开源内部跨端框架 Hippy,打磨三年,日均 PV 过亿 ;
腾讯正式开源图计算框架Plato,十亿级节点图计算进入分钟级时代 ;
写速度提升20%,Elasticsearch 创始人给腾讯云发来感谢信 ;
正式开源!腾讯自研轻量级物联网操作系统 TencentOS tiny,最小体积仅1.8KB!

看业界专家:
竞技世界资深数据库工程师杨建荣:程序员软实力如何构建? 他坚持了2100多天,收获了这些心得 ;
vivo互联网架构师杨振涛: DevOps时代的软件过程改进探讨 ;
心莱科技CEO/CTO李文强: 割裂的云计算服务与其发展理念相悖 ;
北辰时代信息技术有限公司技术总监涂川:十四年从业经验看传统行业落地云计算现状 ;
沪江资深应用架构师王清培: Zookeeper 实现分布式锁安全用法 ;
蘑菇街技术总监赵成: 给运维同学的一个转型建议 ;
猫眼基础架构&基础服务团队负责人陈超: 架构师成长之路之Servicemesh罪与罚 ;
猫眼基础架构&基础服务团队负责人陈超: 架构师成长之路之限流 ;
开源社联合创始人刘天栋: 开源 社区重于代码,应避免“KPI”项目 ;
同程艺龙交通首席架构师李智慧:如何用反应式编程提升系统性能与可用性?
猫眼娱乐基础架构负责人陈超: 图解大型网站技术架构的历史演化过程 ;
同程艺龙机票事业群CTO王晓波: 云上“多活”,同程艺龙应用架构设计与实践 ;
蘑菇街技术总监赵成:做容灾,双活、多活、同城、异地、多云,到底应该怎么选?

感知5G风起:
重磅! 腾讯5G探索地图揭秘 。
2024年视频在移动端流量占比将达74%或更高,将极大促进多媒体技术发展 ;
5G风起,CDN边缘计算将乘风破浪 ;
5G风起,未来数据库有哪些关键词?
5G风起:技术演进背后,标准必要专利扮演着怎样的角色?
5G下CDN技术与产业发展变化,将会如何引领时代发展?
只有5G而没有视频压缩,那么多媒体传输一切都是0 ;

捕捉Serverless前沿:
4招教你基于 Serverless 快速构建应用 ;
Serverless 2.0 重磅发布,无服务器时代来临 ;
Serverless,将给前端发展带来大变革的技术?
Serverless开发者工具上手实践 │ 腾讯云大学 ;
前端学Serverless系列--性能调优 ;
使用 Serverless 进行 AI 预测推理 ;
腾讯云成为Serverless.com大中华区独家合作伙伴!揭秘其背后的技术发展与生态建设 ;
以微信小程序相册为例,看Serverless DevOps最佳实践 ;
掌握Serverless: 基本概念入门 ;
掌握Serverless: 运行原理与组件架构 ;
周维跃: Severless 云函数架构精解(附视频回放) ;

踏上物联网时代:
5G时代,日益重要的边缘计算,你真的了解吗?
边缘计算为什么会迅速占据市场?
说说边缘计算那些事儿│腾讯云大学 ;
腾讯发布智能门锁安全规范,守卫物联网产业链安全 | 附全文下载 ;
腾讯物联网操作系统 TencentOS tiny 架构解析与实践 ;
腾讯云重磅发布“腾讯连连”,以小程序为载体助力万物互联 ;
物联网的未来是什么? 边缘计算助力万物智联(文末有福利) ;
详解腾讯云IoT全栈产品矩阵,6大产品及3大案例 。

面向大前端:
腾讯专家工程师: 2020年,前端发展关键词有哪些?
一行代码解决! iOS二进制重排启动优化 ;
NGW,前端新技术赛场: Serverless SSR 技术内幕 ;
Node部署和运维工作量降低80%,腾讯NOW直播是怎么做到的?
Webpack 4 编译代码如何完美适配 IE 内核 ;
从前端开发到全栈开发;你都了解吗?
秒开率达90%: 腾讯看点客户端 GIF 转视频优化方案 ;
前端工程化实践总结 | QQ音乐商业化Web团队 ;
前端解答>>你真的了解回流和重绘吗?
实时渲染不是梦: 通过共享内存优化Flutter外接纹理的渲染性能 ;
腾讯视频Node.js服务是如何支撑国庆阅兵直播高并发的?
图解浏览器缓存 写给前端工程师的 Flutter 教程 ;

收获小程序技巧:
AI智能客服小程序·云开发实践 ;
大漠穷秋:如何快速构建一款SCRM小程序?
借助云能力,小游戏开发过程是如何升级的?
开发小游戏,云开发真的是银弹吗?
快速实现运营需求,猫眼电影是如何做的?
同程艺龙小程序性能监控系统的探索与实践 ;
微信官方小程序同构新方案Kbone全解析 ;
InfoQ专访腾讯高级工程师: 小程序云开发即将发布实时数据推送服务 ;
我的小游戏开发之路|腾讯TGideas周桂华(花叔) ;
如何快速构建一款联机游戏?
mpvue+云开发,纯前端实现婚礼邀请函小程序 ;
Omi × 云开发『半天』搞定小程序 『markdown 内容发布系统』 ;
Taro + 小程序云开发实战|日语用例助手 ;
单机小游戏变多人联机,原来只需要几分钟!
零部署,零维护,随手记Lite小程序云开发实战分享 ;
巧用云调用,实现【共享名片夹】小程序 ;
如何不用服务器来开发一个小游戏 ;
如何不用服务器来开发一个小游戏 ;
云开发xWePY,快速实现Linux命令查询小程序 ;
云开发定时触发器应用实战 I Moly 订阅助手小程序 ;
云开发实战分享|诗和远方: 旅行小账本云开发 ;
云开发实战分享|一天搭建一个社区 ;
云开发数据库又增新技能!
云调用,小程序鉴权正确姿势 。

把握智慧未来:
AIoT,构建更佳边缘AI能力 ;
AI在K8S中的实践: 云智天枢AI中台架构揭秘 ;
多轮对话机器人打造: 着手设计 ;
多轮对话机器人打造: 话题意图识别 ;
机器学习技术如何推动工业界发展?
基于LSTM的情感识别在鹅漫评论分析中的应用与实践 ;
基于混合集成学习算法的热迁移超时预测模型 ;
厉害了!用腾讯优图AI视觉模组DIY一个属于自己的疲劳驾驶检测仪!
三次大浪潮推进的AI背后对应哪些技术? | 产品经理眼中的AI简史 ;
深入浅出话智能语音识别 | 腾讯云大学大咖分享 ;
腾讯车载小程序:智慧出行的场景化落地 腾讯云人脸融合技术是如何构建的?
腾讯云人脸识别系统在传统行业的应用与落地 ;
腾讯云文字识别OCR技术构建和应用 ;
腾讯云音视频AI技术落地实践全解析 ;
腾讯云知识图谱实践 | 腾讯云大学大咖分享 ;
腾讯云自然语言处理的技术架构与应用 ;
腾讯专家浅谈构建图像识别系统的方法 ;
游戏AI:只是AI间的游戏,还是游戏的未来?
语音识别中CTC算法的基本原理解释 ;
云+ AI 技术在高端酒店行业的应用 ;
智慧城市数字化园区建设,背后实现的技术解析 ;
智能钛机器学习平台在工业和金融行业的落地 ;
云+AI技术助攻,智慧酒店原来是这样的!

剖析数据秘密:
后端开发术语大全 ;
快速读懂innodb存储引擎 ;
(内附独家PPT)李岩: CynosDB高可用系统介绍 ;
【迪B课堂】MySQL运行时系统CPU压力大怎么办?
CynosDB for MySQL 计算存储分离架构的实现和优化 ;
CynosDB分布式存储的核心原理 ;
3分钟学会如何调度运营海量Redis系统 ;
CynosDB for PostgreSQL 一主多读架构设计及优化[内附独家PPT] ;
国际数据库顶会VLDB,解密腾讯TDSQL全时态数据库系统 ;
揭秘全球最大出行业务背后的数据库系统 ;
金融级分布式数据库打造! TDSQL在微众银行的大规模实践 ;
科普:如何正确的选择云数据库?
了解数据库分片(Database Sharding) ;
磊哥测评之数据库SaaS篇: 腾讯云控制台、DMC和小程序 ;
你的数据库,真的安全吗?
深度 | 腾讯云自研数据库技术揭秘 ;
数据库大咖丁奇:MySQL索引存储顺序和order by不一致,怎么办?
想让MySQL跑得更快,更不可忽略这些硬件和系统的优化!
一节课搞懂MySQL数据类型 ;
蛰伏到爆发! 腾讯云数据库获全球“实力竞争者” ;
自研数据库CynosDB可计算智能存储揭秘!
yarn 在快手的应用实践与技术演进之路 ;
腾讯数据库专家雷海林分享智能运维架构 。

构建安全壁垒:
从图文到视频直播,内容风控应如何做到准确、实时、批量鉴黄?
黑灰产技术手段不断“进阶”,如何防御双十二“羊毛党”?
几乎所有企业都要参加的网络安全大考,应该如何准备?
实力认证! 腾讯主导确立全球首个零信任安全国际标准 ;
腾讯专家解读:内容平台如何借助 AI 鉴黄,提升风控能力? | 群分享预告 ;
我们常听说UDP反射攻击,那你听说过TCP反射攻击吗?
如何利用云安全运营中心监测数据泄露 ;
云端安全小建议-使用EMR分析云审计数据 。

盘点架构发展:
企业微服务技术中台落地实践 ;
从技术演变的角度看互联网后台架构 ;
基于Kubernetes 构建.NET Core 技术中台 ;
如何架构海量存储系统 ;
什么是容器云?
如何在Kubernetes中实现容器原地升级 ;
Elasticsearch 7.0 Zen2 开启Elasticsearch分布式新纪元 ;
基于Elastic Stack的日志分析系统 ;
如何基于PaaS快速构建企业运维的工具文化?

掌握音视频趋势:
今年火热的实时音视频技术为什么要和古老的PSTN融合?
视频制作领域——如何提升内容产出效率 ;
腾讯云海外直播系统架构是怎么设计的?(附视频回放)
移动直播连麦技术实践(附视频回放) ;
音视频融合通信技术的最佳实践,全在这里了 ;
张鹏: 腾讯云直播PCDN加速方案(附视频回放) 。

集赞得奖
感谢大家过去一年来的陪伴,为此我们也准备了一些小小的礼物送给大家。请大家到我们的 微信公众号“云加社区”同一篇文章下面参与评论和留言(扫描文章底部二维码即可关注) ,发表你对云加社区过去一年内容的想法或者建议。我们将会 抽选留言区点赞数最多的15位同学,送上爱心公仔。

/活动礼品/
鼠年公仔*5
腾讯云公仔*10

/集赞规则/
1.留言点赞数排名前15的同学将会获得公仔礼品,其中排名前5位得鼠年公仔一份,剩下10位得腾讯云公仔一份;
2.本次集赞活动截止到2020年1月7日中午12:00为止;
3.中奖后请于2020年1月10日前将留言截图及邮寄地址发送至公众号后台,进行兑奖,逾期视为放弃领奖。

技术沙龙大盘点
在最后,我们还为大家准备了一份别样的礼物。整个2019年,社区举办了超过15场线下沙龙,邀请了腾讯及行业内近百位高级工程师、产品负责人等,为大家带来最新的技术分享和行业应用案例。这些内容,我们也统统为大家做好了归纳和盘点。
公众号后台回复“技术沙龙”,即可获得全套内容下载链接 ,感兴趣的小伙伴一定不要错过。
云计算
2020-01-02 11:39:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
作者 | 杨成立(忘篱) 阿里巴巴高级技术专家
关注“阿里巴巴云原生”公众号,回复 Go 即可查看清晰知识大图! 导读 :从问题本身出发,不局限于 Go 语言,探讨服务器中常常遇到的问题,最后回到 Go 如何解决这些问题,为大家提供 Go 开发的关键技术指南。我们将以系列文章的形式推出 《Go 开发的关键技术指南》 ,共有 4 篇文章,本文为第 2 篇。
Could Not Recover
在 C/C++ 中, 最苦恼的 莫过于上线后发现有野指针或内存越界,导致不可能崩溃的地方崩溃; 最无语的 是因为很早写的日志打印,比如 %s 把整数当字符串,突然某天执行到了崩溃; 最无奈的 是无论因为什么崩溃都导致服务的所有用户受到影响。
如果能有一种方案,将指针和内存都管理起来,避免用户错误访问和释放,这样虽然浪费了一部分的 CPU,但是可以在快速变化的业务中避免这些头疼的问题。在现代的高级语言中,比如 Java、Python 和 JS 的异常,以及 Go 的 panic-recover 都是这种机制。
毕竟,用一些 CPU 换得快速迭代中的不 Crash,怎么算都是划得来的。
哪些可以 Recover
Go 有  Defer, Panic, and Recover 。其中 defer 一般用在资源释放或者捕获 panic。而 panic 是中止正常的执行流程,执行所有的 defer,返回调用函数继续 panic;主动调用 panic 函数,还有些运行时错误都会进入 panic 过程。最后 recover 是在 panic 时获取控制权,进入正常的执行逻辑。
注意 recover 只有在 defer 函数中才有用,在 defer 的函数调用的函数中 recover 不起作用,如下实例代码不会 recover: go package main import "fmt" func main() { f := func() { if r := recover(); r != nil { fmt.Println(r) } } defer func() { f() } () panic("ok") }
执行时依旧会 panic,结果如下: $ go run t.go panic: ok goroutine 1 [running]: main.main() /Users/winlin/temp/t.go:16 +0x6b exit status 2
有些情况是不可以被捕获,程序会自动退出,这种都是无法正常 recover。当然,一般的 panic 都是能捕获的,比如 Slice 越界、nil 指针、除零、写关闭的 chan。
下面是 Slice 越界的例子,recover 可以捕获到: go package main import ( "fmt" ) func main() { defer func() { if r := recover(); r != nil { fmt.Println(r) } }() b := []int{0, 1} fmt.Println("Hello, playground", b[2]) }
下面是 nil 指针被引用的例子,recover 可以捕获到: go package main import ( "bytes" "fmt" ) func main() { defer func() { if r := recover(); r != nil { fmt.Println(r) } }() var b *bytes.Buffer fmt.Println("Hello, playground", b.Bytes()) }
下面是除零的例子,recover 可以捕获到: go package main import ( "fmt" ) func main() { defer func() { if r := recover(); r != nil { fmt.Println(r) } }() var v int fmt.Println("Hello, playground", 1/v) }
下面是写关闭的 chan 的例子,recover 可以捕获到: go package main import ( "fmt" ) func main() { defer func() { if r := recover(); r != nil { fmt.Println(r) } }() c := make(chan bool) close(c) c <- true }
Recover 最佳实践
一般 recover 后会判断是否 err,有可能需要处理特殊的 error,一般也需要打印日志或者告警,给一个 recover 的例子: package main import ( "fmt" ) type Handler interface { Filter(err error, r interface{}) error } type Logger interface { Ef(format string, a ...interface{}) } // Handle panic by hdr, which filter the error. // Finally log err with logger. func HandlePanic(hdr Handler, logger Logger) error { return handlePanic(recover(), hdr, logger) } type hdrFunc func(err error, r interface{}) error func (v hdrFunc) Filter(err error, r interface{}) error { return v(err, r) } type loggerFunc func(format string, a ...interface{}) func (v loggerFunc) Ef(format string, a ...interface{}) { v(format, a...) } // Handle panic by hdr, which filter the error. // Finally log err with logger. func HandlePanicFunc(hdr func(err error, r interface{}) error, logger func(format string, a ...interface{}), ) error { var f Handler if hdr != nil { f = hdrFunc(hdr) } var l Logger if logger != nil { l = loggerFunc(logger) } return handlePanic(recover(), f, l) } func handlePanic(r interface{}, hdr Handler, logger Logger) error { if r != nil { err, ok := r.(error) if !ok { err = fmt.Errorf("r is %v", r) } if hdr != nil { err = hdr.Filter(err, r) } if err != nil && logger != nil { logger.Ef("panic err %+v", err) } return err } return nil } func main() { func() { defer HandlePanicFunc(nil, func(format string, a ...interface{}) { fmt.Println(fmt.Sprintf(format, a...)) }) panic("ok") }() logger := func(format string, a ...interface{}) { fmt.Println(fmt.Sprintf(format, a...)) } func() { defer HandlePanicFunc(nil, logger) panic("ok") }() }
对于库如果需要启动 goroutine,如何 recover 呢? 如果不可能出现 panic,可以不用 recover,比如 tls.go 中的一个 goroutine: errChannel <- conn.Handshake()  ; 如果可能出现 panic,也比较明确的可以 recover,可以调用用户回调,或者让用户设置 logger,比如 http/server.go 处理请求的 goroutine: if err := recover(); err != nil && err != ErrAbortHandler {  ; 如果完全不知道如何处理 recover,比如一个 cache 库,丢弃数据可能会造成问题,那么就应该由用户来启动 goroutine,返回异常数据和错误,用户决定如何 recover、如何重试; 如果完全知道如何 recover,比如忽略 panic 继续跑,或者能使用 logger 打印日志,那就按照正常的 panic-recover 逻辑处理。
哪些不能 Recover
下面看看一些情况是无法捕获的,包括(不限于): Thread Limit,超过了系统的线程限制,详细参考下面的说明; Concurrent Map Writers,竞争条件,同时写 map,参考下面的例子。推荐使用标准库的  sync.Map  解决这个问题。
Map 竞争写导致 panic 的实例代码如下: go package main import ( "fmt" "time" ) func main() { m := map[string]int{} p := func() { defer func() { if r := recover(); r != nil { fmt.Println(r) } }() for { m["t"] = 0 } } go p() go p() time.Sleep(1 * time.Second) } 注意:如果编译时加了  -race ,其他竞争条件也会退出,一般用于死锁检测,但这会导致严重的性能问题,使用需要谨慎。 备注:一般标准库中通过  throw  抛出的错误都是无法 recover 的,搜索了下 Go1.11 一共有 690 个地方有调用 throw。
Go1.2 引入了能使用的最多线程数限制  ThreadLimit ,如果超过了就 panic,这个 panic 是无法 recover 的。 fatal error: thread exhaustion runtime stack: runtime.throw(0x10b60fd, 0x11) /usr/local/Cellar/go/1.8.3/libexec/src/runtime/panic.go:596 +0x95 runtime.mstart() /usr/local/Cellar/go/1.8.3/libexec/src/runtime/proc.go:1132
默认是 1 万个物理线程,我们可以调用  runtime  的  debug.SetMaxThreads  设置最大线程数。 SetMaxThreads sets the maximum number of operating system threads that the Go program can use. If it attempts to use more than this many, the program crashes. SetMaxThreads returns the previous setting. The initial setting is 10,000 threads.
用这个函数设置程序能使用的最大系统线程数,如果超过了程序就 crash,返回的是之前设置的值,默认是 1 万个线程。 The limit controls the number of operating system threads, not the number of goroutines. A Go program creates a new thread only when a goroutine is ready to run but all the existing threads are blocked in system calls, cgo calls, or are locked to other goroutines due to use of runtime.LockOSThread.
注意限制的并不是 goroutine 的数目,而是使用的系统线程的限制。goroutine 启动时,并不总是新开系统线程,只有当目前所有的物理线程都阻塞在系统调用、cgo 调用,或者显示有调用  runtime.LockOSThread  时。 SetMaxThreads is useful mainly for limiting the damage done by programs that create an unbounded number of threads. The idea is to take down the program before it takes down the operating system.
这个是最后的防御措施,可以在程序干死系统前把有问题的程序干掉。
举一个简单的例子,限制使用 10 个线程,然后用  runtime.LockOSThread  来绑定 goroutine 到系统线程,可以看到没有创建 10 个 goroutine 就退出了(runtime 也需要使用线程)。参考下面的例子 Playground: ThreadLimit: go package main import ( "fmt" "runtime" "runtime/debug" "sync" "time" ) func main() { nv := 10 ov := debug.SetMaxThreads(nv) fmt.Println(fmt.Sprintf("Change max threads %d=>%d", ov, nv)) var wg sync.WaitGroup c := make(chan bool, 0) for i := 0; i < 10; i++ { fmt.Println(fmt.Sprintf("Start goroutine #%v", i)) wg.Add(1) go func() { c <- true defer wg.Done() runtime.LockOSThread() time.Sleep(10 * time.Second) fmt.Println("Goroutine quit") }() <- c fmt.Println(fmt.Sprintf("Start goroutine #%v ok", i)) } fmt.Println("Wait for all goroutines about 10s...") wg.Wait() fmt.Println("All goroutines done") }
运行结果如下: bash Change max threads 10000=>10 Start goroutine #0 Start goroutine #0 ok ...... Start goroutine #6 Start goroutine #6 ok Start goroutine #7 runtime: program exceeds 10-thread limit fatal error: thread exhaustion runtime stack: runtime.throw(0xffdef, 0x11) /usr/local/go/src/runtime/panic.go:616 +0x100 runtime.checkmcount() /usr/local/go/src/runtime/proc.go:542 +0x100 ...... /usr/local/go/src/runtime/proc.go:1830 +0x40 runtime.startm(0x1040e000, 0x1040e000) /usr/local/go/src/runtime/proc.go:2002 +0x180
从这次运行可以看出,限制可用的物理线程为 10 个,其中系统占用了 3 个物理线程,user-level 可运行 7 个线程,开启第 8 个线程时就崩溃了。 注意这个运行结果在不同的 go 版本是不同的,比如 Go1.8 有时候启动 4 到 5 个 goroutine 就会崩溃。
而且加 recover 也无法恢复,参考下面的实例代码。
可见这个机制是最后的防御,不能突破的底线。我们在线上服务时,曾经因为 block 的 goroutine 过多,导致触发了这个机制。 go package main import ( "fmt" "runtime" "runtime/debug" "sync" "time" ) func main() { defer func() { if r := recover(); r != nil { fmt.Println("main recover is", r) } } () nv := 10 ov := debug.SetMaxThreads(nv) fmt.Println(fmt.Sprintf("Change max threads %d=>%d", ov, nv)) var wg sync.WaitGroup c := make(chan bool, 0) for i := 0; i < 10; i++ { fmt.Println(fmt.Sprintf("Start goroutine #%v", i)) wg.Add(1) go func() { c <- true defer func() { if r := recover(); r != nil { fmt.Println("main recover is", r) } } () defer wg.Done() runtime.LockOSThread() time.Sleep(10 * time.Second) fmt.Println("Goroutine quit") }() <- c fmt.Println(fmt.Sprintf("Start goroutine #%v ok", i)) } fmt.Println("Wait for all goroutines about 10s...") wg.Wait() fmt.Println("All goroutines done") }
如何避免程序超过线程限制被干掉?一般可能阻塞在 system call,那么什么时候会阻塞?还有, GOMAXPROCS  又有什么作用呢? The GOMAXPROCS variable limits the number of operating system threads that can execute user-level Go code simultaneously. There is no limit to the number of threads that can be blocked in system calls on behalf of Go code; those do not count against the GOMAXPROCS limit. This package's GOMAXPROCS function queries and changes the limit. GOMAXPROCS sets the maximum number of CPUs that can be executing simultaneously and returns the previous setting. If n < 1, it does not change the current setting. The number of logical CPUs on the local machine can be queried with NumCPU. This call will go away when the scheduler improves.
可见 GOMAXPROCS 只是设置 user-level 并行执行的线程数,也就是真正执行的线程数 。实际上如果物理线程阻塞在 system calls,会开启更多的物理线程。关于这个参数的说明,文章《 Number of threads used by goroutine 》解释得很清楚: There is no direct correlation. Threads used by your app may be less than, equal to or more than 10. So if your application does not start any new goroutines, threads count will be less than 10. If your app starts many goroutines (>10) where none is blocking (e.g. in system calls), 10 operating system threads will execute your goroutines simultaneously. If your app starts many goroutines where many (>10) are blocked in system calls, more than 10 OS threads will be spawned (but only at most 10 will be executing user-level Go code).
设置 GOMAXPROCS 为 10:如果开启的 goroutine 小于 10 个,那么物理线程也小于 10 个。如果有很多 goroutines,但是没有阻塞在 system calls,那么只有 10 个线程会并行执行。如果有很多 goroutines 同时超过 10 个阻塞在 system calls,那么超过 10 个物理线程会被创建,但是只有 10 个活跃的线程执行 user-level 代码。
那么什么时候会阻塞在 system blocking 呢?例子《 Why does it not create many threads when many goroutines are blocked in writing 》解释很清楚,虽然设置了 GOMAXPROCS 为 1,但实际上还是开启了 12 个线程,每个 goroutine 一个物理线程,具体执行下面的代码 Writing Large Block: go package main import ( "io/ioutil" "os" "runtime" "strconv" "sync" ) func main() { runtime.GOMAXPROCS(1) data := make([]byte, 128*1024*1024) var wg sync.WaitGroup for i := 0; i < 10; i++ { wg.Add(1) go func(n int) { defer wg.Done() for { ioutil.WriteFile("testxxx"+strconv.Itoa(n), []byte(data), os.ModePerm) } }(i) } wg.Wait() }
运行结果如下: Mac chengli.ycl$ time go run t.go real 1m44.679s user 0m0.230s sys 0m53.474s 虽然 GOMAXPROCS 设置为 1,实际上创建了 12 个物理线程。 有大量的时间是在 sys 上面,也就是 system calls。 So I think the syscalls were exiting too quickly in your original test to show the effect you were expecting.
Effective Go  中的解释: Goroutines are multiplexed onto multiple OS threads so if one should block, such as while waiting for I/O, others continue to run. Their design hides many of the complexities of thread creation and management.
由此可见,如果程序出现因为超过线程限制而崩溃,那么可以在出现瓶颈时,用 linux 工具查看系统调用的统计,看哪些系统调用导致创建了过多的线程。
Errors
错误处理是现实中经常碰到的、难以处理好的问题,下面会从下面几个方面探讨错误处理: 为什么 Go 没有选择异常,而是返回错误码(error)? 因为异常模型很难看出有没有写对,错误码方式也不容易,相对会简单点。 Go 的 error 有什么问题,为何 Go2 草案这么大篇幅说 error 改进? 因为 Go 虽然是错误码但还不够好,问题在于啰嗦、繁杂、缺失关键信息。 有哪些好用的 error 库,如何和日志配合使用? 推荐用库  pkg/errors ;另外,避免日志和错误混淆。 Go 的错误处理最佳实践是什么? 配合日志使用错误。错误需要带上上下文、堆栈等信息。
错误和异常
我们总会遇到非预期的非正常情况,有一种是符合预期的,比如函数返回 error 并处理,这种叫做可以预见到的错误,还有一种是预见不到的比如除零、空指针、数组越界等叫做 panic,panic 的处理主要参考  Defer, Panic, and Recover 。
错误处理的模型一般有两种,一般是错误码模型比如 C/C++ 和 Go,还有异常模型比如 Java 和 C#。Go 没有选择异常模型,因为错误码比异常更有优势,参考文章《 Cleaner, more elegant, and wrong 》以及《 Cleaner, more elegant, and harder to recognize 》。
看下面的代码: try { AccessDatabase accessDb = new AccessDatabase(); accessDb.GenerateDatabase(); } catch (Exception e) { // Inspect caught exception } public void GenerateDatabase() { CreatePhysicalDatabase(); CreateTables(); CreateIndexes(); }
这段代码的错误处理有很多问题,比如如果  CreateIndexes  抛出异常,会导致数据库和表不会删除,造成脏数据。从代码编写者和维护者的角度看这两个模型,会比较清楚:
错误处理不容易做好,要说容易那说明做错了;要把错误处理写对了,基于错误码模型虽然很难,但比异常模型简单。
如果使用错误码模型,非常容易就能看出错误处理没有写对,也能很容易知道做得好不好;要知道是否做得非常好,错误码模型也不太容易。
如果使用异常模型,无论做的好不好都很难知道,而且也很难知道怎么做好。

Errors in Go
Go 官方的 error 介绍,简单一句话就是返回错误对象的方式,参考《 Error handling and Go 》,解释了 error 是什么?如何判断具体的错误?以及显式返回错误的好处。
文中举的例子就是打开文件错误: func Open(name string) (file *File, err error)
Go 可以返回多个值,最后一个一般是 error,我们需要检查和处理这个错误,这就是 Go 的错误处理的官方介绍: if err := Open("src.txt"); err != nil { // Handle err }
看起来非常简单的错误处理,有什么难的呢?稍等,在 Go2 的草案中,提到的三个点  Error Handling 、 Error Values 和  Generics 泛型 ,两个点都是错误处理的,这说明了 Go1 中对于错误是有改进的地方。
再详细看下 Go2 的草案, 错误处理:Error Handling  中,主要描述了发生错误时的重复代码,以及不能便捷处理错误的情况。比如草案中举的这个例子 No Error Handling: CopyFile,没有做任何错误处理: package main import ( "fmt" "io" "os" ) func CopyFile(src, dst string) error { r, _ := os.Open(src) defer r.Close() w, _ := os.Create(dst) io.Copy(w, r) w.Close() return nil } func main() { fmt.Println(CopyFile("src.txt", "dst.txt")) }
还有草案中这个例子 Not Nice and still Wrong: CopyFile,错误处理是特别啰嗦,而且比较明显有问题: package main import ( "fmt" "io" "os" ) func CopyFile(src, dst string) error { r, err := os.Open(src) if err != nil { return err } defer r.Close() w, err := os.Create(dst) if err != nil { return err } defer w.Close() if _, err := io.Copy(w, r); err != nil { return err } if err := w.Close(); err != nil { return err } return nil } func main() { fmt.Println(CopyFile("src.txt", "dst.txt")) }
当  io.Copy  或  w.Close  出现错误时,目标文件实际上是有问题,那应该需要删除 dst 文件的。而且需要给出错误时的信息,比如是哪个文件,不能直接返回 err。所以 Go 中正确的错误处理,应该是这个例子 Good: CopyFile,虽然啰嗦繁琐不简洁: package main import ( "fmt" "io" "os" ) func CopyFile(src, dst string) error { r, err := os.Open(src) if err != nil { return fmt.Errorf("copy %s %s: %v", src, dst, err) } defer r.Close() w, err := os.Create(dst) if err != nil { return fmt.Errorf("copy %s %s: %v", src, dst, err) } if _, err := io.Copy(w, r); err != nil { w.Close() os.Remove(dst) return fmt.Errorf("copy %s %s: %v", src, dst, err) } if err := w.Close(); err != nil { os.Remove(dst) return fmt.Errorf("copy %s %s: %v", src, dst, err) } return nil } func main() { fmt.Println(CopyFile("src.txt", "dst.txt")) } 具体应该如何简洁的处理错误,可以读  Error Handling ,大致是引入关键字 handle 和 check,由于本文重点侧重 Go1 如何错误处理,就不展开分享了。
明显上面每次都返回的  fmt.Errorf  信息也是不够的,所以 Go2 还对于错误的值有提案,参考  Error Values 。大规模程序应该面向错误编程和测试,同时错误应该包含足够的信息。
Go1 中判断 error 具体是什么错误,有以下几种办法: 直接比较,比如返回的是  io.EOF  这个全局变量,那么可以直接比较是否是这个错误; 可以用类型转换 type 或 switch,尝试来转换成具体的错误类型,看是哪种错误; 提供某些函数来判断是否是某个错误,比如  os.IsNotExist  判断是否是指定错误; 当多个错误被糅合到一起时,只能用  error.Error()  返回的字符串匹配,看是否是某个错误。
在复杂程序中,有用的错误需要包含调用链的信息。例如,考虑一次数据库写,可能调用了 RPC,RPC 调用了域名解析,最终是没有权限读  /etc/resolve.conf  文件,那么给出下面的调用链会非常有用: write users database: call myserver.Method: \ dial myserver:3333: open /etc/resolv.conf: permission denied

Errors Solutions
由于 Go1 的错误值没有完整的解决这个问题,才导致出现非常多的错误处理的库,比如: 2017 年 12 月, upspin.io/errors,带逻辑调用堆栈的错误库,而不是执行的堆栈,引入了  errors.Is 、 errors.As  和  errors.Match ; 2015 年 12 月, github.com/pkg/errors,带堆栈的错误,引入了  %+v  来格式化错误的额外信息比如堆栈; 2014 年 10 月, github.com/hashicorp/errwrap,可以 wrap 多个错误,引入了错误树,提供 Walk 函数遍历所有的错误; 2014 年 2 月, github.com/juju/errgo,Wrap 时可以选择是否隐藏底层错误。和  pkg/errors  的 Cause 返回最底层的错误不同,它只反馈错误链的下一个错误; 2013 年 7 月, github.com/spacemonkeygo/errors,是来源于一个大型 Python 项目,有错误的 hierarchies,自动记录日志和堆栈,还可以带额外的信息。打印错误的消息比较固定,不能自己定义; 2019 年 9 月, Go1.13  标准库扩展了 error,支持了 Unwrap、As 和 Is,但没有支持堆栈信息。
Go1.13 改进了 errors,参考如下实例代码: package main import ( "errors" "fmt" "io" ) func foo() error { return fmt.Errorf("read err: %w", io.EOF) } func bar() error { if err := foo(); err != nil { return fmt.Errorf("foo err: %w", err) } return nil } func main() { if err := bar(); err != nil { fmt.Printf("err: %+v\n", err) fmt.Printf("unwrap: %+v\n", errors.Unwrap(err)) fmt.Printf("unwrap of unwrap: %+v\n", errors.Unwrap(errors.Unwrap(err))) fmt.Printf("err is io.EOF? %v\n", errors.Is(err, io.EOF)) } }
运行结果如下: err: foo err: read err: EOF unwrap: read err: EOF unwrap of unwrap: EOF err is io.EOF? true
从上面的例子可以看出: 没有堆栈信息,主要是想通过 Wrap 的日志来标识堆栈,如果全部 Wrap 一层和堆栈差不多,不过对于没有 Wrap 的错误还是无法知道调用堆栈; Unwrap 只会展开第一个嵌套的 error,如果错误有多层嵌套,取不到最里面的那个 error,需要多次 Unwrap 才行; 用  errors.Is  能判断出是否是最里面的那个 error。
另外,错误处理往往和 log 是容易混为一谈的,因为遇到错误一般会打日志,特别是在 C/C 中返回错误码一般都会打日志记录下,有时候还会记录一个全局的错误码比如 linux 的 errno,而这种习惯,导致 error 和 log 混淆造成比较大的困扰。考虑以前写了一个 C 的服务器,出现错误时会在每一层打印日志,所以就会形成堆栈式的错误日志,便于排查问题,如果只有一个错误,不知道调用上下文,排查会很困难: avc decode avc_packet_type failed. ret=3001 Codec parse video failed, ret=3001 origin hub error, ret=3001
这种比只打印一条日志  origin hub error, ret=3001  要好,但是还不够好: 和 Go 的错误一样,比较啰嗦,有重复的信息。如果能提供堆栈信息,可以省去很多需要手动写的信息; 对于应用程序可以打日志,但是对于库,信息都应该包含在 error 中,不应该直接打印日志。如果底层的库都要打印日志,将会导致底层库都要依赖日志库,这时很多库都有日志打印函数供调用者重写; 对于多线程,看不到线程信息,或者看不到业务层 ID 的信息。对于服务器来说,有时候需要知道这个错误是哪个连接的,从而查询这个连接之前的上下文信息。
改进后的错误日志变成了在底层返回,而不在底层打印在调用层打印,有调用链和堆栈,有线程切换的 ID 信息,也有文件的行数: Error processing video, code=3001 : origin hub : codec parser : avc decoder [100] video_avc_demux() at [srs_kernel_codec.cpp:676] [100] on_video() at [srs_app_source.cpp:1076] [101] on_video_imp() at [srs_app_source:2357] 从 Go2 的描述来说,实际上这个错误处理也还没有考虑完备。从实际开发来说,已经比较实用了。
总结下 Go 的 error,错误处理应该注意以下几点: 凡是有返回错误码的函数,必须显式的处理错误,如果要忽略错误,也应该显式的忽略和写注释; 错误必须带丰富的错误信息,比如堆栈、发生错误时的参数、调用链给的描述等等。特别要强调变量,我看过太多日志描述了一对常量,比如 "Verify the nonce, timestamp and token of specified appid failed",而这个消息一般会提到工单中,然后就是再问用户,哪个 session 或 request 甚至时间点?这么一大堆常量有啥用呢,关键是变量呐; 尽量避免重复的信息,提高错误处理的开发体验,糟糕的体验会导致无效的错误处理代码,比如拷贝和漏掉关键信息; 分离错误和日志,发生错误时返回带完整信息的错误,在调用的顶层决定是将错误用日志打印,还是发送到监控系统,还是转换错误,或者忽略。

Best Practice
推荐用  github.com/pkg/errors  这个错误处理的库,基本上是够用的,参考 Refine: CopyFile,可以看到 CopyFile 中低级重复的代码已经比较少了: package main import ( "fmt" "github.com/pkg/errors" "io" "os" ) func CopyFile(src, dst string) error { r, err := os.Open(src) if err != nil { return errors.Wrap(err, "open source") } defer r.Close() w, err := os.Create(dst) if err != nil { return errors.Wrap(err, "create dest") } nn, err := io.Copy(w, r) if err != nil { w.Close() os.Remove(dst) return errors.Wrap(err, "copy body") } if err := w.Close(); err != nil { os.Remove(dst) return errors.Wrapf(err, "close dest, nn=%v", nn) } return nil } func LoadSystem() error { src, dst := "src.txt", "dst.txt" if err := CopyFile(src, dst); err != nil { return errors.WithMessage(err, fmt.Sprintf("load src=%v, dst=%v", src, dst)) } // Do other jobs. return nil } func main() { if err := LoadSystem(); err != nil { fmt.Printf("err %+v\n", err) } } 改写的函数中,用  errors.Wrap  和  errors.Wrapf  代替了  fmt.Errorf ,我们不记录 src 和 dst 的值,因为在上层会记录这个值(参考下面的代码),而只记录我们这个函数产生的数据,比如  nn 。 import "github.com/pkg/errors" func LoadSystem() error { src, dst := "src.txt", "dst.txt" if err := CopyFile(src, dst); err != nil { return errors.WithMessage(err, fmt.Sprintf("load src=%v, dst=%v", src, dst)) } // Do other jobs. return nil } 在这个上层函数中,我们用的是  errors.WithMessage  添加了这一层的错误信息,包括  src  和  dst ,所以  CopyFile  里面就不用重复记录这两个数据了。同时我们也没有打印日志,只是返回了带完整信息的错误。 func main() { if err := LoadSystem(); err != nil { fmt.Printf("err %+v\n", err) } } 在顶层调用时,我们拿到错误,可以决定是打印还是忽略还是送监控系统。
比如我们在调用层打印错误,使用  %+v  打印详细的错误,有完整的信息: err open src.txt: no such file or directory open source main.CopyFile /Users/winlin/t.go:13 main.LoadSystem /Users/winlin/t.go:39 main.main /Users/winlin/t.go:49 runtime.main /usr/local/Cellar/go/1.8.3/libexec/src/runtime/proc.go:185 runtime.goexit /usr/local/Cellar/go/1.8.3/libexec/src/runtime/asm_amd64.s:2197 load src=src.txt, dst=dst.txt
但是这个库也有些小毛病: CopyFile  中还是有可能会有重复的信息,还是 Go2 的  handle  和  check  方案是最终解决; 有时候需要用户调用  Wrap ,有时候是调用  WithMessage (不需要加堆栈时),这个真是非常不好用的地方(这个我们已经修改了库,可以全部使用 Wrap 不用 WithMessage,会去掉重复的堆栈)。

Logger
一直在码代码,对日志的理解总是不断在变,大致分为几个阶段: 日志是给人看的,是用来查问题的。出现问题后根据某些条件,去查不同进程或服务的日志。日志的关键是不能漏掉信息,漏了关键日志,可能就断了关键的线索; 日志必须要被关联起来,上下文的日志比单个日志更重要。长连接需要根据会话关联日志;不同业务模型有不同的上下文,比如服务器管理把服务器作为关键信息,查询这个服务器的相关日志;全链路跨机器和服务的日志跟踪,需要定义可追踪的逻辑 ID; 海量日志是给机器看的,是结构化的,能主动报告问题,能从日志中分析潜在的问题。日志的关键是要被不同消费者消费,要输出不同主题的日志,不同的粒度的日志。日志可以用于排查问题,可以用于告警,可以用于分析业务情况。 Note: 推荐阅读 Kafka 对于 Log 的定义,广义日志是可以理解的消息, The Log: What every software engineer should know about real-time data's unifying abstraction 。
完善信息查问题
考虑一个服务,处理不同的连接请求: package main import ( "context" "fmt" "log" "math/rand" "os" "time" ) type Connection struct { url string logger *log.Logger } func (v *Connection) Process(ctx context.Context) { go checkRequest(ctx, v.url) duration := time.Duration(rand.Int()%1500) * time.Millisecond time.Sleep(duration) v.logger.Println("Process connection ok") } func checkRequest(ctx context.Context, url string) { duration := time.Duration(rand.Int()%1500) * time.Millisecond time.Sleep(duration) logger.Println("Check request ok") } var logger *log.Logger func main() { ctx := context.Background() rand.Seed(time.Now().UnixNano()) logger = log.New(os.Stdout, "", log.LstdFlags) for i := 0; i < 5; i++ { go func(url string) { connecton := &Connection{} connecton.url = url connecton.logger = logger connecton.Process(ctx) }(fmt.Sprintf("url #%v", i)) } time.Sleep(3 * time.Second) }
这个日志的主要问题,就是有了和没有差不多,啥也看不出来,常量太多变量太少,缺失了太多的信息。看起来这是个简单问题,却经常容易犯这种问题,需要我们在打印每个日志时,需要思考这个日志比较完善的信息是什么。上面程序输出的日志如下: 2019/11/21 17:08:04 Check request ok 2019/11/21 17:08:04 Check request ok 2019/11/21 17:08:04 Check request ok 2019/11/21 17:08:04 Process connection ok 2019/11/21 17:08:05 Process connection ok 2019/11/21 17:08:05 Check request ok 2019/11/21 17:08:05 Process connection ok 2019/11/21 17:08:05 Check request ok 2019/11/21 17:08:05 Process connection ok 2019/11/21 17:08:05 Process connection ok
如果完善下上下文信息,代码可以改成这样: type Connection struct { url string logger *log.Logger } func (v *Connection) Process(ctx context.Context) { go checkRequest(ctx, v.url) duration := time.Duration(rand.Int()%1500) * time.Millisecond time.Sleep(duration) v.logger.Println(fmt.Sprintf("Process connection ok, url=%v, duration=%v", v.url, duration)) } func checkRequest(ctx context.Context, url string) { duration := time.Duration(rand.Int()%1500) * time.Millisecond time.Sleep(duration) logger.Println(fmt.Sprintf("Check request ok, url=%v, duration=%v", url, duration)) }
输出的日志如下: 2019/11/21 17:11:35 Check request ok, url=url #3, duration=32ms 2019/11/21 17:11:35 Check request ok, url=url #0, duration=226ms 2019/11/21 17:11:35 Process connection ok, url=url #0, duration=255ms 2019/11/21 17:11:35 Check request ok, url=url #4, duration=396ms 2019/11/21 17:11:35 Check request ok, url=url #2, duration=449ms 2019/11/21 17:11:35 Process connection ok, url=url #2, duration=780ms 2019/11/21 17:11:35 Check request ok, url=url #1, duration=1.01s 2019/11/21 17:11:36 Process connection ok, url=url #4, duration=1.099s 2019/11/21 17:11:36 Process connection ok, url=url #3, duration=1.207s 2019/11/21 17:11:36 Process connection ok, url=url #1, duration=1.257s
上下文关联
完善日志信息后,对于服务器特有的一个问题,就是如何关联上下文,常见的上下文包括: 如果是短连接,一条日志就能描述,那可能要将多个服务的日志关联起来,将全链路的日志作为上下文; 如果是长连接,一般长连接一定会有定时信息,比如每隔 5 秒输出这个链接的码率和包数,这样每个链接就无法使用一条日志描述了,链接本身就是一个上下文; 进程内的逻辑上下文,比如代理的上下游就是一个上下文,合并回源,故障上下文,客户端重试等。
以上面的代码为例,可以用请求 URL 来作为上下文。 package main import ( "context" "fmt" "log" "math/rand" "os" "time" ) type Connection struct { url string logger *log.Logger } func (v *Connection) Process(ctx context.Context) { go checkRequest(ctx, v.url) duration := time.Duration(rand.Int()%1500) * time.Millisecond time.Sleep(duration) v.logger.Println(fmt.Sprintf("Process connection ok, duration=%v", duration)) } func checkRequest(ctx context.Context, url string) { duration := time.Duration(rand.Int()%1500) * time.Millisecond time.Sleep(duration) logger.Println(fmt.Sprintf("Check request ok, url=%v, duration=%v", url, duration)) } var logger *log.Logger func main() { ctx := context.Background() rand.Seed(time.Now().UnixNano()) logger = log.New(os.Stdout, "", log.LstdFlags) for i := 0; i < 5; i++ { go func(url string) { connecton := &Connection{} connecton.url = url connecton.logger = log.New(os.Stdout, fmt.Sprintf("[CONN %v] ", url), log.LstdFlags) connecton.Process(ctx) }(fmt.Sprintf("url #%v", i)) } time.Sleep(3 * time.Second) }
运行结果如下所示: [CONN url #2] 2019/11/21 17:19:28 Process connection ok, duration=39ms 2019/11/21 17:19:28 Check request ok, url=url #0, duration=149ms 2019/11/21 17:19:28 Check request ok, url=url #1, duration=255ms [CONN url #3] 2019/11/21 17:19:28 Process connection ok, duration=409ms 2019/11/21 17:19:28 Check request ok, url=url #2, duration=408ms [CONN url #1] 2019/11/21 17:19:29 Process connection ok, duration=594ms 2019/11/21 17:19:29 Check request ok, url=url #4, duration=615ms [CONN url #0] 2019/11/21 17:19:29 Process connection ok, duration=727ms 2019/11/21 17:19:29 Check request ok, url=url #3, duration=1.105s [CONN url #4] 2019/11/21 17:19:29 Process connection ok, duration=1.289s
如果需要查连接 2 的日志,可以 grep 这个  url #2  关键字: Mac:gogogo chengli.ycl$ grep 'url #2' t.log [CONN url #2] 2019/11/21 17:21:43 Process connection ok, duration=682ms 2019/11/21 17:21:43 Check request ok, url=url #2, duration=998ms
然鹅,还是发现有不少问题: 如何实现隐式标识,调用时如何简单些,不用没打一条日志都需要传一堆参数? 一般 logger 是公共函数(或者是每个类一个 logger),而上下文的生命周期会比 logger 长,比如 checkRequest 是个全局函数,标识信息必须依靠人打印,这往往是不可行的; 如何实现日志的 logrotate(切割和轮转),如何收集多个服务器日志。
解决办法包括: 用  Context  的 WithValue 来将上下文相关的 ID 保存,在打印日志时将 ID 取出来; 如果有业务特征,比如可以取 SessionID 的 hash 的前 8 个字符形成 ID,虽然容易碰撞,但是在一定范围内不容易碰撞; 可以变成 json 格式的日志,这样可以将 level、id、tag、file、err 都变成可以程序分析的数据,送到 SLS 中处理; 对于切割和轮转,推荐使用  lumberjack  这个库,程序的 logger 只要提供  SetOutput(io.Writer)  将日志送给它处理就可以了。 当然,这要求函数传参时需要带 context.Context,一般在自己的应用程序中可以要求这么做,凡是打日志的地方要带 context。对于库,一般可以不打日志,而返回带堆栈的复杂错误的方式,参考  Errors  错误处理部分。
点击下载《不一样的 双11 技术:阿里巴巴经济体云原生实践》
本书亮点 双11 超大规模 K8s 集群实践中,遇到的问题及解决方法详述 云原生化最佳组合:Kubernetes+容器+神龙,实现核心系统 100% 上云的技术细节 双 11 Service Mesh 超大规模落地解决方案 “ 阿里巴巴云原生 关注微服务、Serverless、容器、Service Mesh 等技术领域、聚焦云原生流行技术趋势、云原生大规模的落地实践,做最懂云原生开发者的技术圈。”
云计算
2019-12-26 11:15:00