各类系统架构演化

一、整体趋势
天下大势,分久必合合久必分。复杂有复杂的代价,简单有简单的代价。

看这些年的发展,也能看出一定的端倪:
单体应用【一统天下】-》业务复杂后,应用拆分【应用拆分】-》SOA、ESB【接口拆分聚合】-》微服务【服务拆分】-》中台【业务聚合】-》去中台【业务不要瞎聚合,再拆回去】

微服务的简单,其实是将服务功能单一化,确实降低了编码及编码维护难度,也是有很高的代价的。我们不仅要享受简单,也要知道其代价。最直观的几个代价有:
1、需要很高的代价去打造适合自己的微服务治理体系
2、由于需要解决单点问题,每个服务要部署多个实例即使用量达不到也要备着
3、运维复杂度上升,需要工具支持,对人员要求也更高;
4、微服务的调用链,比单体应用的调用链,复杂,效率低,排错难;
使用微服务,对运维能力是有一定要求的,而且非所有软件都应该走微服务的路子。盲目的微服务化,服务拆分过细的结果,往往是又合了回来。

究其原因,人对复杂事务的掌握是很有局限性的,所以我们倾向于将复杂问题分解为简单问题,将问题标准化,然后解决问题。整个人类社会都是这样,才会有社会专业分工。但分工又不能太细,这样才能恰到好处的发挥人的创造力。最终都是多种因素折中的结果。

无论是软件开发,还是CPU制造,还是工厂制造商品,还是粮食的生产,原理是相似的。

二、单体程序还是微服务
个人认为,以后的发展趋势是单体系统、微服务架构,会相互学习相互补足。

很多典型的单体系统如操作系统、数据库、中间件如MQ等,在内部设计时也会根据功能进行拆分模块,并不会全部放到一起实现,会尽力降低某一部分出错后导致的后果;同时在分布式环境下,也会给出自己不错的答案。

而微服务正相反,其本身确实是降低了开放的难度,提升了开放效率,隔离做的很好。但同时要看到,微服务将不少工作扔给了运维,没有强力的运维,微服务拆的越细,最后死的也是越难看。所以在微服务化的过程中,很多项目都是拆细了又合并回来,接受了单体组建的风险,避免了运维工作的指数型增长。

单体程序的问题在于,单体应用如果太复杂了,人是很难考虑到各个环节的,需要用更复杂的手段,来进行保证,如火箭、飞机、航母,这些复杂的软硬件一体的开放工作,需要额外的系统及理论及代价去确保系统的可靠性。而且单体系统最长见的一个问题就是,单体系统的稳定性,小于单体系统中最差部分的稳定性,在很多没有良好管控的软件项目上,单体系统的稳定性,其实就是最差的那段代码决定的。人员水平不足,项目经验不足,人力不足,时间不足,是不是和你的项目很像?这种情况下,复杂的单体系统难以高效稳定的发展。

微服务,通过各种拆分隔离解决的这个问题,但同时在运维的维度引入了新的复杂度。每个项目都应该在单体服务稳定性与运维复杂度、资源耗费之间,取得一个平衡。孰优孰劣,应该按项目来确定,而不是一棍子打死。

此外,单片机给人的感觉是单体吧,但加个设备网关,搞个IoT不香么?

同样,各种复杂的操作,云平台给你封装好了,应用只需要找平台要这要那,对于应用来说,平台把内部实现封成了黑盒子,一定意义上说不是单体么?

三、SOA为何衰败

SOA的想做的事情,方向是对的,到今天,很多思想都在落地。

但那种“干这种改变世界的事情,老子要发明一套让所有人看起来都高大上的技术才行”的想法,让SOA过于复杂。

反而不如务实,用尽了少的代价让想法落地。

不应该为了解决简单的问题,一开始就弄一巨复杂的规范,SOA如此,EJB也是如此。

重点没有去解决问题,而是为了规范而去制定规范。甚至为了制定规范,还要发明一堆名词,发明一种语言。

比如说,SOA什么都要管理,就需要发明WSDL来框定业务。WSDL根本就不是人类容易看懂的,而且十分严格。如果通讯双方namespace有个字母大小写不一致,通讯就失败,报错根本看不懂。查几个小时,发现就一个字母大小写问题。问题是你只想发个字符串过去啊。

成功的规范,往往是先落地用起来,然后一点一点长大的;先出一套巨复杂的规范,然后回去套业务,得到的不是惊喜,往往是惊吓。

四、微服务和DevOPS
一个微服务团队,从头到尾完全管理好自己的服务,其实是很难的一件事情。实际中,不少团队,微服务的技术用了不少,但DevOPS其实都没算做起来,仍然十分的依赖运维团队来完成很多工作,自建的微服务平台也不够好用,有了问题人仰马翻的。

感觉这个阶段,不如直接花钱买云厂商的服务,规避部分技术风险,重点发展业务,会更好一些。等有些技术积累了,再做适合自己的技术解决方案。

五、今天的云原生还有哪些主要矛盾,下一次软件架构的进化将会主要解决什么问题?

个人认为,当前的架构,还远远没有达到最优状态。真正最优的时候,绝大多数开发人员,是不需要知道K8S、ISTIO、Spring、数据库、缓存及各类中间件的存在的。

之前的状态是,硬件按硬件的思路发展,系统及虚拟化按其思路发展,应用架构也是按自己的思路发展。之所以当前需要发明很多新技术,是硬件、系统及虚拟化、应用架构彼此之间都有一条鸿沟,那大家就都需要自己造轮子搭梯子解决问题,统一问题必然有N中解法。后面相互磨合久了,鸿沟会进一步变窄,直至平滑过渡。

应用层开发人员,会在很大的技术视野内专注于业务层,其他层的需求只需要简单调用服务即可。

但不久的将来,一定会面临新的巨坑,需要填。

六、FAAS趋势

个人对FAAS有种直觉上的偏见,感觉有些走向了另一个极端:
1、如果保持F很简单,那平台确实可以直接将功能封装为通用服务,但就算加上DSL,F可以施展的空间感觉也不大
2、如果让F变复杂,支持复杂业务,那FAAS其实就退化成为了功能十分单一的微服务,还不如直接微服务来的爽快
整体感觉就是有些鸡肋,一边有纯图形化工具或标准服务,一边是正常程度的编码,夹在中间。

这个真的很像当年ESB的思维方式,给了一堆的标准组件,给人一看感觉封装的真好,不用编程也能做好多事情啊。但用过就知道,稍微复杂的业务就要自己去扩展组件了,不会编程什么都做不了。最后就是换了一个技术平台写代码而已。

各种类型的调度

硬件层面:
1、操作系统管理进程,分配CPU、内存等资源
2、计算线程与CPU的绑定,也算一种调度吧

网络层面:
1、路由器交换机的配置、包括F5的配置
2、服务器或虚拟机组成Cluster或NLB
3、Nginx反向代理时不同节点分配不同的比重,加上访问策略,也算一种调度吧。
4、类似的Dubbo等框架,也支持不同的调度方式

任务层面:
1、做大数据处理的时候,接触过Yarn
2、Kubernetes中Pod调度及驱逐机制
3、线程池、socket连接池、DB连接池、对象池之类的管理好像也能算

此外,在调度的过程中,有一个规律,那就是调度的时候,大家都倾向于,将代码和数据放到一起进行。
在数据量小的时候,大家倾向于把数据搬运到代码附近,比如CPU的调度。
但数据量大的时候,尤其是到了大数据、云计算时代,我们反而倾向于,把代码搬运到数据附近,因为这样更合算。

各类语言泛型实现方式

1、C
通过void*和强制类型转换来实现,也可以通过宏来实现类似功能,一般只会用作简单的实现

2、C++
模板类的模板具体化,预编译时生成不同的类,分别进行编译,会造成代码膨胀。
但由于不需要在运行时进行任何的类型识别,程序也会变得比较干净,运行效率也更高。

3、JAVA
基于类型擦除,在编译泛型类时将强制消除类型信息;
JVM运行时泛型类并不知道自己处理的具体是何种类型。

4、Go
当前版本不支持泛型,可以通过 interface{}强制转换实现泛型。
也可以通过第三方库,实现模板库类型的泛型。

5、C#
泛型类在编译生成中间码IL时,通用类型T只是一个占位符。
在实例化类时,根据实际数据类型代替T并由即时编译器(JIT)生成本地代码运行,不同封闭类的本地代码是不一样的。

6、JS、Python
动态类型天然支持泛型

使用JDK动态代理时为何必须实现至少一个接口

这个问题,就要去看一下OpenJDK的源码了:

//在Proxy类里中:
//constructorParams的定义如下:
private static final Class<?>[] constructorParams = { InvocationHandler.class };

//newProxyInstance无限精简之后就是:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
        throws IllegalArgumentException {
    //通过ProxyClassFactory调用ProxyGenerator生成了代理类
    Class<?> cl = getProxyClass0(loader, interfaces);
    //找到参数为InvocationHandler.class的构造函数
    final Constructor<?> cons = cl.getConstructor(constructorParams);
    //创建代理类实例
    return cons.newInstance(new Object[]{h});
}

//在ProxyGenerator类中:
public static byte[] generateProxyClass(final String name,Class<?>[] interfaces, int accessFlags)){}
private byte[] generateClassFile() {}

//上面两个方法,做的就是:
//将接口全部进行代理
//并生成其他需要的方法,比如上面用到的构造函数、toString、equals、hashCode等
//生成对应的字节码
//其实这也就说明了,为何JDK的动态代理,必须需要至少一个接口

Jetty源码分析01

一、Jetty的ScopedHandler的doStart方法,最后一步是将线程私有变量__outerScope设置成null,为什么需要这样做呢?

protected void doStart() throws Exception
{
    try{
        _outerScope=__outerScope.get();
        if (_outerScope==null){
           //本次处理的第一个scope handler
           //告知后续scope handler,_outerScope选我
            __outerScope.set(this);
        }
        super.doStart();
        _nextScope= getChildHandlerByClass(ScopedHandler.class);
    }
    finally
    {
        if (_outerScope==null){
           //本次处理结束
           //为了下次同一个线程处理是,
           //还能正常的设置第一个scope handler
           //必须把threadlocal变量设为null
            __outerScope.set(null);
        }
    }
}

二、Jetty中,ScopedHandler中nextHandle调用顺序是如何的?

//此外,这一节里有一个non scoped handler X,一开始没太看懂调阅顺序。
//后来发现是这样的:
public final void nextHandle(String target...)...
{
    if (_nextScope!=null && _nextScope==_handler){
        //上面条件可以保证下一个handler是scope handler
        _nextScope.doHandle(target,baseRequest,request, response);
    }
    else if (_handler!=null){
        //non scpoe handler调用下一个handler的
        super.handle(target,baseRequest,request,response);
    }
}

感觉类成员的命名不太合适,
比如__outerScope和_outerScope
比如_handler其实一直指向的是下一个handler,是不是该为_nextHandler更好一些?

Java的各种容器

对比一下,Servlet容器、Spring容器和SpringMVC容器。

Servlet容器,是用于管理Servlet生命周期的。
Spring容器,是用于管理Spring Bean生命周期的。
SpringMVC容器,适用于管理SpringMVC Bean生命周期的。

Tomcat/Jetty启动,对于每个WebApp,依次进行初始化工作:
1、对每个WebApp,都有一个WebApp ClassLoader,和一个ServletContext
2、ServletContext启动时,会扫描web.xml配置文件,找到Filter、Listener和Servlet配置

3、如果Listener中配有spring的ContextLoaderListener
3.1、ContextLoaderListener就会收到webapp的各种状态信息。
3.3、在ServletContext初始化时,ContextLoaderListener也就会将Spring IOC容器进行初始化,管理Spring相关的Bean。
3.4、ContextLoaderListener会将Spring IOC容器存放到ServletContext中

4、如果Servlet中配有SpringMVC的DispatcherServlet
4.1、DispatcherServlet初始化时(其一次请求到达)。
4.2、其中,DispatcherServlet会初始化自己的SpringMVC容器,用来管理Spring MVC相关的Bean。
4.3、SpringMVC容器可以通过ServletContext获取Spring容器,并将Spring容器设置为自己的根容器。而子容器可以访问父容器,从而在Controller里可以访问Service对象,但是在Service里不可以访问Controller对象。
4.2、初始化完毕后,DispatcherServlet开始处理MVC中的请求映射关系。

我面试的时候,有段时间经常会问一个很坑问题,Servlet默认是单例模式的,Spring的Bean默认是单例模式的,那Spring MVC是如何处理并发请求的呢?

Servlet容器、Spring容器、SpringMVC容器之间的关系

之前在极客时间上回答老师的问题:

Servlet容器,是用于管理Servlet生命周期的。
Spring容器,是用于管理Spring Bean生命周期的。
SpringMVC容器,适用于管理SpringMVC Bean生命周期的。

Tomcat/Jetty启动,对于每个WebApp,依次进行初始化工作:
1、对每个WebApp,都有一个WebApp ClassLoader,和一个ServletContext
2、ServletContext启动时,会扫描web.xml配置文件,找到Filter、Listener和Servlet配置

3、如果Listener中配有Spring的ContextLoaderListener
3.1、ContextLoaderListener就会收到webapp的各种状态信息。
3.3、在ServletContext初始化时,ContextLoaderListener也就会将Spring IOC容器进行初始化,管理Spring相关的Bean。
3.4、ContextLoaderListener会将Spring IOC容器存放到ServletContext中

4、如果Servlet中配有SpringMVC的DispatcherServlet
4.1、DispatcherServlet初始化时(其一次请求到达)。
4.2、其中,DispatcherServlet会初始化自己的SpringMVC容器,用来管理Spring MVC相关的Bean。
4.3、SpringMVC容器可以通过ServletContext获取Spring容器,并将Spring容器设置为自己的根容器。而子容器可以访问父容器,从而在Controller里可以访问Service对象,但是在Service里不可以访问Controller对象。
4.2、初始化完毕后,DispatcherServlet开始处理MVC中的请求映射关系。

windows下无法启动pip

尤其是在有多版本Python共存的情况下,修改windows修改环境变量后,经常会导致pip无法启动的情况。
此时,不仅是pip,Python/Scripts目录下的所有脚本都无法启动,并会有如下错误:

Fatal error in launcher: Unable to create process using '"'

其根本原因,其实十分简单,pip无法找到python.exe可执行程序,你可以看pip的源码来确认这一点。
有几种方法可以解决这个问题:

1、环境变量法,更适合单Ptyhon环境
将python.exe路径,增加到PATH环境变量中即可解决问题

2、脚本启动法,适合多个Ptyhon环境

set PATH=PATH_TO_PYTHON\;PATH_TO_PYTHON\Scripts;%PATH%
python -m pip install XXX

3、用1或2,更新pip,可以解决问题(对单Python环境更适用)

python -m pip install --upgrade pip

4、修改pip二进制文件
用十六进制编辑工具打开pip.exe
修改python.exe路径
保存

5、用PE编辑器修改pip二进制文件
同方法4

6、解压
用解压工具解压pip,
得到__main__.py
重命名为pip.py
运行

python pip.py install XXX

浅谈CPP智能指针

智能指针其实并不是指针,而是一个特殊对象。
在智能指针对象生命期即将结束时,它会调用析构函数释放有它管理的堆内存。
访问智能指针管理对象的方法,使用操作符“->”(重载)。
访问智能指针本来的方法,使用操作符“.”。

我们常见的智能指针有以下几种:

C98
std::auto_ptr
第一代智能指针,有些操作比如“=”坑比较多,不推荐使用。

C11
std::unique_ptr
独占对象,并保证指针所指对象生命周期与其一致。

std::shared_ptr
可共享指针对象,可以赋值给shared_ptr或weak_ptr。
通过引用计数的方式控制生命周期,当指针所指对象的所有的shared_ptr生命周期结束时(引用计数为0时)会被销毁。

std::weak_ptr
可以指向shared_ptr,但并不影响引用计数。
不影像所指对象的生命周期,在引用所指对象时,先用需要lock()才能使用。

Boost
不共享对象,类似于std::unique_ptr
boost::scoped_ptr
boost::scoped_array

共享对象,类似于std::shared_ptr
boost::shared_ptr
boost::shared_array

共享对象,但不改变对象引用计数,类似于std::weak_ptr
boost::weak_ptr

侵入式引用计数,要求使用对象自己实现计数功能
boost::intrusive_ptr

下面给一个例子,说明一下std下的四种智能指针。
1、SmartPointerTest.cpp

#include <memory>
#include <iostream>
#include "MyTest.h"

using namespace std;

void test_auto_ptr()
{
	std::auto_ptr<MyTest> auto_ptr_01(new MyTest("tom", 20));

	if (auto_ptr_01.get())
	{
		auto_ptr_01->sayHello();
		auto_ptr_01.get()->_name = "jerry";
		auto_ptr_01->sayHello();
		(*auto_ptr_01)._age += 1;
		auto_ptr_01->sayHello();
	}
	
	//auto_ptr_02会抢占auto_ptr_01的对象
	//此后auto_ptr_01不指向MyTest对象
	std::auto_ptr<MyTest> auto_ptr_02 = auto_ptr_01;
	if (auto_ptr_01.get())
	{
		cout << "auto_ptr_01 is released" << endl;
	}
	auto_ptr_02->sayHello();

	//只是释放所有权,并不释放内存
	//MyTest* test = auto_ptr_02.release();

	//释放内存
	auto_ptr_02.reset();
	if (!auto_ptr_01.get())
	{
		cout <<"auto_ptr_02 is released"<< endl;
	}
	
}

void test_unique_ptr()
{
	//独占对象
	//保证指针所指对象生命周期与其一致
	unique_ptr<MyTest> unique_ptr_01(new MyTest("tom", 20));
	unique_ptr_01->sayHello();

	//不允许直接做右值
	//unique_ptr<int> unique_ptr_02 = unique_ptr_01;

	//需要通过move来处理
	unique_ptr<MyTest> unique_ptr_03 = move(unique_ptr_01);
	if (!unique_ptr_01)cout << "unique_ptr_01 is empty" << endl;
	unique_ptr_03->sayHello();

	//释放指针
	unique_ptr_03.reset();
	if (!unique_ptr_03)cout << "unique_ptr_03 is empty" << endl;
}

void test_shared_ptr()
{
	shared_ptr<MyTest> shared_ptr_01(make_shared<MyTest>("tom", 20));
	shared_ptr<MyTest> shared_ptr_02 = shared_ptr_01;
	shared_ptr_01->sayHello();
	shared_ptr_02->sayHello();

	shared_ptr_01.reset();
	if (!shared_ptr_01)cout << "shared_ptr_01 is empty" << endl;
	shared_ptr_02->sayHello();

	shared_ptr_02.reset();
	if (!shared_ptr_02)cout << "shared_ptr_02 is empty" << endl;
}

void test_weak_ptr()
{
	shared_ptr<MyTest> shared_ptr_01(make_shared<MyTest>("tom", 20));
	weak_ptr<MyTest> weak_ptr_01 = shared_ptr_01;
	shared_ptr_01->sayHello();
	weak_ptr_01.lock()->sayHello();

	weak_ptr_01.reset();
	if (!weak_ptr_01.lock())cout << "weak_ptr_01 is empty" << endl;
	shared_ptr_01->sayHello();
	
	weak_ptr<MyTest> weak_ptr_02 = shared_ptr_01;
	weak_ptr<MyTest> weak_ptr_03 = weak_ptr_02;
	if(weak_ptr_01.lock())weak_ptr_02.lock()->sayHello();
	shared_ptr_01.reset();
	if (!weak_ptr_01.lock())cout << "weak_ptr_02 is empty" << endl;
}

int _tmain(int argc, _TCHAR* argv[])
{
	test_auto_ptr();
	test_unique_ptr();
	test_shared_ptr();
	test_weak_ptr();

	return 0;
}

2、MyTest.h

#pragma once

#include <iostream>
#include <string>

using namespace std;

class MyTest
{
public:
	MyTest(string name, int age);
	~MyTest();
	void sayHello();

public:
	string _name;
	int _age;
};

3、MyTest.cpp

#include "stdafx.h"
#include "MyTest.h"

MyTest::MyTest(string name, int age)
{
	_name = name;
	_age = age;
}

MyTest::~MyTest()
{
}

void MyTest::sayHello()
{
	cout << "Hello " << _name<< "! You are "<< _age <<" years old." << endl;
}

PS:
聪明的你有没有发下,CPP的智能指针,与JVM内存中的四种引用方式,强引用、软引用、弱引用,虚引用,有很多相似的地方呢?