0%

istio与envoy的关系

服务网格中的概念分为控制平面和数据平面,控制平面是istio,数据平面是envoy。

service-mesh

图片摘自 istio.io

控制平面的作用可以理解为配置中心server(或者说管理server),负责配置的获取和配置的下发。数据平面就是服务侧的透明代理,所有服务的流量都通过数据平面进行转发。

istio作为以k8s为基础的服务网格,配置项的设置是通过CRD(CustomResourceDefine)实现,配置下发到envoy是通过xDS协议,envoy通过gRPC stream订阅istio中的配置。

流程图如下:

Multiple EDS requests on the same stream

注意是envoy去istio发现配置,而不是istio将配置发送给envoy,使用gRPC是为了更低延迟的更新配置

xds协议的介绍,图片摘自envoy官方文档: https://www.envoyproxy.io/docs/envoy/latest/api-docs/xds_protocol#xds-protocol

istio也有支持其他数据平面的趋势,需要数据平面支持xds协议,但是目前看来istio还是离不开envoy,就像k8s目前离不开docker一样,两者的关系也有一定的相似。

istio的优势

个人感觉istio最大的优势是简化了envoy的配置,原始的envoy配置是十分复杂的,有一定的使用成本,istio在这方面做了抽象,减少了使用者的使用成本。

比如: authorizationpolicies.security.istio.io这个CRD通过结合envoy的http-filter的ext_authz和rbac filter实现了对服务的自定义认证,简化了其中大量的配置,大大的减少了使用成本.

优先推荐使用gRPC的方式实现外部认证server,istio对于http的外部认证server的一些配置支持的比较慢,当想要使用http server实现方式时,有些配置在envoy中有,但是istio中没有,可能不得不去了解envoy的文档,自己写略微复杂一点的envoy filter,当然其中的调试工作也是比较复杂的。

https://github.com/istio/istio/issues/27790#event-5281877115

基本概念

MySQL锁的大分类为两类: 悲观锁与乐观锁。两种锁实现的位置不同,悲观锁由数据库系统实现,乐观锁由用户自己实现。

使用场景

普遍的使用场景是: select&update, select出数据然后再此基础上进行更新操作,此期间需要使用锁来保证同一个事务的ACID (https://zh.wikipedia.org/wiki/ACID)

悲观锁

据从事证券行业的朋友说,证券银行等行业都是使用的悲观锁

悲观锁又分为两种: 共享锁与排他锁,顾名思义,这两种锁的严格级别(颗粒度)也是从低到高。

共享锁: lock in share mode

其他的session可以读取数据,但是不能修改,如果其他事务修改了该行数据,会重新读取最新的数据

排他锁: lock for update

相对于共享锁更加严格,其他session不能读也不能修改。

乐观锁

乐观锁一般由使用方来实现,实现方式是为数据库表增加一个version字段(自动更新), select时把version字段也带上,然后在update的时候再次去检查数据库中的version与事务中select出来的version是否一致,如果不一致就获取最新的version重试。

阿里java开发规范里有一则规范是乐观锁的重试次数不低于3次,可以作为参考

对比优势与劣势

TODO

示例

示例数据

create table `goods` (
id int unsigned auto_increment comment '自增主键',
name varchar(35) not null default '' comment '商品名字',
stock int unsigned not null default 0 comment '商品库存',
create_time timestamp not null default current_timestamp comment '创建时间',
update_time timestamp not null default current_timestamp on update current_timestamp comment '更新时间',
primary key (`id`),
unique key `uk__name` (`name`)
) engine=innodb default charset=utf8mb4 comment='商品表';

insert into `goods` (`name`, `stock`) values ('phone', 10000);
insert into `goods` (`name`, `stock`) values ('book', 1000);

关掉事务自动提交

set sesssion autocommit=0;

悲观锁

事务A:

start transaction;
select name as name, @a := stock as stock from goods where name = 'phone' for update;
update goods set stock=@a-1 where name = 'phone';
commit;

事务B:

start transaction;
select name as name, @a:=stock as stock from goods where name = 'phone' for update; # 这里事务B开始阻塞,直到事务A提交
update goods set stock=@a-1 where name = 'phone';
commit;
注意点

where条件必须加索引,加上索引是行锁,否则就是锁表。索引类型可以是主键索引,唯一索引,普通索引。

草稿:

flask-login类库涉及到flask的session对象,flasksession的源码在flask.sessions中,其中最重要的是open_session,save_session函数。顾名思义,open_session是解密的函数,save_session是加密的函数, 加解密使用的是itsdangerous库,使用到的是URLSafeTimedSerializer类。URLSafeTimedSerializer中重要的函数可以从以下几个开始看:

  • load_payload: decode的数据
  • dump_payload:encode数据
  • make_signer: 创建一个签名类实例,调用signunsign方法来给字符串签名,以及验证签名合法,raise BadSignature,默认会以b'.'来将encode的数据和签名连接起来作为加密后的值。

dump_payload

源码位于URLSafeSerializerMixin.dump_payload, 具体过程如下:

  • 调用超类的dump_payload即json.dumps(obj, separators=(‘,’, ‘:’))
  • 得到json再调用zlib.compress进行压缩, 如果压缩后的长度比原来小就替换
  • 调用itsdangerous.base64_encode函数将json字符串encode,该函数主要是调用`base64.urlsafe_b64encode(b’xxx’).strip(b’=’)
  • 如果json是被压缩过的,在json前加一个b’.’, base64d = b’.’ + base64d,然后将字符串return

load_payload

dupm_payload的反过程

sign

unsign

守护进程

守护进程(daemon)生存期长的一种进程。它们没有控制终端,所以说它们是在后台运行的。使用Python做一个守护进程简单方法是fork, 但是有forkdouble fork magic

fork:
import os
import sys

pid = os.fork() # 只fork一次,然后主进程退出,子进程称为孤儿进程被init进程接管,在后台运行。
if pid > 0:
sys.exit()
...
do_something_in_child_process()
double-fork:
import os
import sys

os.umask(0) # ①

pid = os.fork() # ②
if pid > 0: # 父进程退出
sys.exit()

os.setsid() # ③
pid = os.fork()
if pid > 0:
sys.exit() # 第一次fork的子进程退出

os.chdir('/') # ④
si = open('/dev/null', 'r')
so = open('/dev/null', 'a+')
se = open('/dev/null', 'a+')

os.dup2(si.fileno(), sys.stdin.fileno())
os.dup2(so.fileno(), sys.stdout.fileno())
os.dup2(se.fileno(), sys.stderr.fileno())

daemon() # 孙子进程在后台开始运行。

① 修改文件权限,具体看场景,为什么要修改权限,因为继承来的权限可能会被设置为拒绝某些权限(不过大家都是用root起进程的,你懂得😉)。

② 调用fork使父进程exit。为什么?第一,让shell认为该命令已经执行完毕。第二,保证子进程不是一个进程组的组长进程。这是setsid调用的先决条件。

③ 调用setsid创建一个新会话(会话是干嘛的?留个坑以后填)。调用setsid的效果是,第一,成为新会话的首进程,第二,成为一个新进程的组长进程,第三,没有控制终端。

此时调用了第二次fork,这是为了防止守护进程获得控制终端。

④ 修改当前工作目录为根目录,修改stdin, stdout, stderr等。(why? 留坑)

为什么要double-fork,在基于System V的系统中,有些人建议再次fork,终止父进程,继续使用子进程中的守护进程。这就保证了该守护进程不是会话首进程,可以防止它获取控制终端。

参见《UNIX环境高级编程》- - - 13.3编程规则。

此处的问题以及解答摘自《Python学习手册 第4版》

1.什么是元类?

元类是用来创建一个类的类。常规的类默认的是type类的实例。元类通常是type类的子类,它重新定义了类创建协议方法,以便定制在一条class语句的末尾发布的类创建调用(创建类的__call__()函数);它通常会重新定义__new__和__init__方法以接入创建类的协议,元类也可以以其他的方式编码—例如,作为简单函数,但是他们负责为新类创建和返回一个对象。

一句话版本:

元类是创建类的类,元类可以为它所有的实例类统一添加方法或执行代码.

2.如何声明一个类的元类?

在Python3.0 及以后的版本中,使用一个关键字参数: class(metaclass=M)。在Python2.x中,使用类属性: __metaclass__ = M 。

3.在管理类方面,类装饰器如何与元类重叠?

由于二者都是在一条class语句的末尾自动触发的,因此类装饰器和元类都可以用来管理类。装饰器把一个类名重新绑定到一个可调用对象的结果,而元类把类创建指向一个可调用对象,但它们都是可以用做相同目的的钩子。要管理类,装饰器直接扩展并返回最初的类对象。元类在创建一个类之后扩展它。

类装饰器:

def tracer(cls):
class Wrapper(object):
def __init__(self, *args):
self.wrapped = cls(*args)
def __getattr__(self, name):
print("Getting the {} of {}".format(name, self.wrapped))
return getattr(self.wrapped, name)
return Wrapper

@tracer
class Spam(object):
pass

元类:

class Meta(type):
def __new__(meta, classname, supers, classdict):
do_something()
classdict[some_attrs] = attr
return type.__new__(classname, supers, classdict)

class Spam(object):
__metaclass__ = Meta
pass

4.在管理实例方面,类装饰器如何与元类重叠?

由于二者都是在一条class语句的末尾自动触发的,因此类装饰器和元类都可以用来管理类实例,通过插入一个包装器对象来捕获实例创建调用。装饰器把类名重新绑定到一个可调用对象,而该可调用对象在实例创建时运行以保持最初的类对象。元类可以做同样的事情,但是,它们必须也创建类对象,因此,它们用在这一角色中更复杂一些。