首先给大家分享一个巨牛巨牛的人工智能教程,是我无意中发现的。教程不仅零基础,通俗易懂,而且非常风趣幽默,还时不时有内涵段子,像看小说一样,哈哈~我正在学习中,觉得太牛了,所以分享给大家!点这里可以跳转到教程
Mac OS默认已安装好了ruby,所以可直接运行一下命令:
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" 1 但是在安装过程中,会提示如下错误:
xcode-select: error: invalid developer directory'/Library/Developer/CommandLine Tools' 12 解决办法如下: 1. 从App Store安装Xcode软件; 2. 安装完成后,打开Xcode软件,接受licence; 3. 然后运行如下命令,确认Xcode的版本;
# 确认Xcode的版本ls -l /Applications/|grep Xcode 12 根据上面的结果,确定执行命令的目录; # Xcode.app就是上面过滤的结果sudo xcode-select -s /Applications/Xcode.app/Contents/Developer 12 浏览人工智能教程
laravel中使用env函数将会导致缓存配置文件或者路由无法使用,建议使用config替代构造函数中直接去获取session(‘name’)将不会获取的到所要的值,因为先执行了构造函数再执行中间件,解决方案,在构造函数中执行中间件,再获取session数据 public function __construct() { $this->middleware(function ($request, $next) { session('name'); return $next($request); }); config/session.php配置文件中 expire_on_close可配置关闭浏览器后session状态 ,false重启浏览器后session有效,true关闭浏览器后立即失效数据较少的情况下感觉数据库连接查询数据慢,如果使用的是localhost 换成127.0.0.1试试,访问速度明显加快app/Http/Kernel.php 文件下 throttle配置api请求的频率 'api' => [ 'throttle:60,1', ], Laravel开启了debug模式,但是发生错误时显示空白页,原因是laravel的错误日志文件storage/logs/权限设置不正确laravel 邮件类发送不了可能原因: 25端口链接不上原因是阿里云默认封杀了25端口链接不上465端口错误:Connection to smtp.exmail.qq.com:465 Timed Out
config/mail.php 中的tls 改为ssl
Laravel Eloquent setTable 无效的问题 // User 表为 users $model = new User(); $model->setTable('prefix_'.'users'); $model = $model->first(); $model->name = '';// 设置一个名称 $model->save();// 此时,save()的是 users表而不是 'prefix_users'表 Laravel分表ORM操作参考
以上为一些laravel中常见的坑,后续会整理更多
Java定义字符串 不论使用哪种形式创建字符串,字符串对象一旦被创建,其值是不能改变的,但可以使用其他变量重新赋值的方式进行更改。
直接定义字符串 直接定义字符串是指使用双引号表示字符串中的内容,如:
String str="Hello Java"; 或者
String str;str="Heilo Java"; 字符串变量必须经过初始化才能使用。
使用 String 类定义 具体定义参考 String 类源码。
Java 字符串常用操作 字符串查找 根据字符查找
String提供了两种查找字符串的方法,即indexOf与lastIndexOf方法。
1. indexOf() 方法
indexOf() 方法用于返回字符串在指定字符串中首次出现的索引位置,如果能找到,则返回索引值,否则返回 -1。该方法主要有两种重载形式:
str.indexOf(value)str.indexOf(value,int fromIndex) 其中,str 表示指定字符串;value 表示待查找的字符串;fromIndex 表示查找时的起始索引,如果不指定 fromIndex,则默认从指定字符串中的开始位置(即 fromIndex 默认为 0)开始查找。
2. lastlndexOf() 方法
lastIndexOf() 方法用于返回字符串在指定字符串中最后一次出现的索引位置,如果能找到则返回索引值,否则返回 -1。该方法也有两种重载形式:
str.lastIndexOf(value)str.lastlndexOf(value, int fromIndex) 注意:lastIndexOf() 方法的查找策略是从右往左查找,如果不指定起始索引,则默认从字符串的末尾开始查找。
根据索引查找
String 类的 charAt() 方法可以在字符串内根据指定的索引查找字符,该方法的语法形式如下:
字符串名.charAt(索引值) 获取子字符串 String 类的 substring() 方法用于对字符串进行提取,该方法主要有两种重载形式:
1. substring(int beginIndex) 形式
此方式用于提取从索引位置开始至结尾处的字符串部分。调用时,括号中是需要提取字符串的开始位置,方法的返回值是提取的字符串。例如:
String str="我爱 Java 编程"
break 语句 在 Java 中,break 语句有 3 种作用,分别是:在 switch 语句中终止一个语句序列、使用 break 语句直接强行退出循环和使用 break 语句实现 goto 的功能。
在 switch 语句中终止一个语句序列 在 switch 语句中终止一个语句序列,就是在每个 case 子句块的最后添加语句“break;”,
使用 break 语句直接强行退出循环 可使用 break 语句强行退出循环,忽略循环体中的任何其他语句和循环的条件判断。在循环中遇到 break 语句时,循环被终止。
在一系列嵌套循环中使用 break 语句时,它将仅仅终止最里面的循环。
一个循环中可以有一个以上的 break 语句,但是过多的 break 语句会破坏代码结构。switch 循环语句中的 break 仅影响 switch 语句,不会影响循环。
使用 break 语句实现 goto 的功能 break 语句可以实现 goto 的功能,通过使用扩展的 break 语句,可以终止执行一个或者几个任意代码块,这些代码块不必是一个循环或一个 switch 语句的一部分。同时这种扩展的 break 语句带有标签,可以明确指定从何处重新开始执行。
break 除了具有 goto 退出深层循环嵌套作用外,还保留了一些程序结构化的特性。
标签 break 语句的通用格式如下:
break label; label 是标识代码块的标签。当执行这种形式的 break 语句时,控制权被传递出指定的代码块。被加标签的代码块必须包围 break 语句,但是它不需要直接包围 break 的块。也就是说,可以使用一个加标签的 break 语句来退出一系列的嵌套块,但是不能使用 break 语句将控制权传递到不包含 break 语句的代码块。
题目一:
现有多组整数数组,需要将他们合并成一个新的数组。合并规则,从每个数组按顺序
取出固定长度的内容合并到新的数组中,取完的内容会删除掉,如果该行不足固定长度
或者已经为空,则直接取出剩余部分的内容放到新的数组中,继续下行。如样例1,
获得长度3,先遍历第一行,获得2.5.6 ;再遍历第二行,获得1,7.4;再循环回到第行.
获得7,9,5 ;再遍历第二行,获得1,7,4;再循环回到第1行,获得7.按顺序拼接成最终结果
输入描述:
第一行是每次读职的固定长度,长度>0 第2-n行是需要合并的数组,不同的数组用回车换行分隔,数组内部用建号分隔
输出描述:
输出一新的数组,用逗号分隔。
输入:
3
2,5,6,7,9,5,7
1,7,4,3,4
输出:
2,5,6,1,7,4,7,9,5,3,4,7
#include<iostream> #include<vector> #include<string> using namespace std; int main()//本程序还可以改进,使用string的find函数查找','号至少可以减少n/2的查找次数 { string temp; string res; vector<string> array; int clen = 0;//每次截取字符串长度 cin >> clen; while (cin >> temp)//vector接收字符串 { array.push_back(temp); } int flag = 0,pos=0; int num = 0;//,计数 while (1)//循环截取每行字符串中的元素,直到所有字符串为空 { flag = 0; int size = array.size(); for (int i = 0; i<size; ++i)//选取某一行元素 { num = 0; if (!
前段时间,接到了一个任务,负责开展事业部技术总监职位的竞聘活动,故整理了技术总监的技术要求,希望符合条件的同学们积极参与竞聘。现把测试技术总监的要求分享给各位,一起交流。
由于我们主要做网络设备,所以相关技术要求以网络相关知识为主,至于其它领域的测试技术总监要求则可以类比。
我认为测试技术总监需要具备两大方面的能力。
测试工程:
1、基础能力:具备扎实的基础知识,如通信、计算机、网络、编程语言、软件工程、测试理论、测试技术等;熟练使用网络仪表、通用网络设备以及各类测试工具等;
2、技术能力:精通某个或多个测试技术领域,如白盒测试、系统测试、专项测试、自动化测试等,具备技术攻关能力,发现并解决所负责领域的关键难题;
3、业务能力:精通某个或多个业务领域,如网络安全,流量处理,智能网卡,态势感知等,拥有比较丰富的业务测试实践经验;
4、架构设计:能够独立进行大型/复杂软件项目的测试架构设计,对所负责的领域具有规划的能力,能够推动产品测试技术持续优化和不断演进;
5、方向掌控:根据公司总体发展战略,制定本领域技术发展战略,引领本领域的技术方向,确保技术方向的正确性和可持续发展性;
个人软能力:
1、沟通交流:掌握沟通交流的技巧和方法,熟悉演讲和辩论的技巧,具备良好的沟通交流能力和技术输出能力;
2、思考能力:拥有独立思考的能力和系统化的思维,能够对本领域的问题提出深刻见解,从根本上解决存在的矛盾;
3、学习能力:拥有开放的心态和持续学习的恒心,对技术有浓厚的兴趣,密切跟踪最新技术发展态势,不断完善自我知识体系;
4、体系优化:熟悉研发流程,参与公司流程规范的建设和产品/技术/流程的优化改进,提高团队工作效率;
5、管理能力:指导、培养低级别的工程师,做好技术资产积累和梯队建设,促进并形成完整的技术支撑队伍;
总之,测试技术总监更多的还是偏测试技术方向把控、测试架构设计、测试技术的难题解决、测试技术的实践和应用、测试技术的挖掘和创新,不断的使用技术手段来提高工作的效率,进而为部门创造更好的绩效。
前几天去华为实习面试(虽然笔试0AC,还是收到面试通知),现在记录一下面试心得,后面继续好好准备。
实习面试分技术面和综合面:
1.感觉技术面试重视基础,问c++的基础知识比较多,其它的比如网络通信和多线程都是根据简历中问,感觉问的也不是特别深入。
2.笔试没通过的题下来一定要再做一遍,我一上来面试官就让我再把之前没通过的题再手写一遍,好在头天都敲过一遍,但是手写还是出了一点小错误。
3.简单的数据结构和算法要会手写,一起去面试的同学一般都会要求手写常用的排序查找算法,或者在很大的二维数组找最大值,或者字符串排序、翻转之类的。
4.因为看简历知道我会数据库,还让我手写基本的sql语句,比如查找某学生中学号含有两个1的学生。 所以简历中写上的东西,就一定要有准备,感觉一轮技术面如果连续问你几个问题你都不会的话,很有可能凉。
5.综合面试的话问项目比较多,然后就是问你遇到的困难如何解决,后来的发展规划等等,聊的内容相对轻松,不过也有淘汰人的。
6.对每个项目中涉及的知识点和遇到的问题都要有所准备。
7.简历要求彩印双面打印,最好装一个文件袋里,我去了看其他同学这么打印才知道的,尴尬。
整体上来说感觉华为面试相对腾讯的要简单,华为的面试更重视基础,腾讯的面试问地更深入和广泛 (至于阿里和字节跳动,我直接笔试挂,连面试机会都没有)。
总之准备好简历中相关的知识和C++常考的基础知识面试时候就会更轻松,还有就是谈吐要自信一点,问题不明白的要和面试官交流。
重载>> 和 <<
#include <iosfwd> // 或 #include <iostream> using std::istream; using std::ostream; friend istream& operator>> (istream& , Myclass& ); friend ostream& operator<< (ostream& , const Myclass& ); 运算符重载举例
//fraction.h #ifndef FRACTION_H #define FRACTION_H #include <iostream> class Fraction { friend std::ostream& operator<< (std::ostream& out, const Fraction& f); public: Fraction(long n=0, long d=1); virtual ~Fraction(); Fraction operator- () const//重载负号(一元运算符重载) { return Fraction(-num, den); } Fraction operator+ (const Fraction& f) const //重载加号(二元运算符重载) { return Fraction(num*f.
声明:原文出处已在文末标出,本人出于学习,对其做了整理,收集干货,不作商业用途!
Java 是一种类型安全语言,编译器存储在变量中的数值具有适当的数据类型。
数据类型的分类 Java 语言的数据类型分为两种:基本数据类型和引用数据类型。
(1) 基本数据类型包括 boolean(布尔型)、float(单精度浮点型)、char(字符型)、byte(字节型)、short(短整型)、int(整型)、long(长整型)和 double (双精度浮点型)共 8 种,详见表 1 所示:
表1 Java的基本数据类型 类型名称关键字占用内存取值范围字节型byte1 字节-128~127短整型short2 字节-32768~32767整型int4 字节-2147483648~2147483647长整型long8 字节-9223372036854775808L~9223372036854775807L单精度浮点型float4 字节+/-3.4E+38F(6~7 个有效位)双精度浮点型double8 字节+/-1.8E+308 (15 个有效位)字符型char2 字节ISO 单一字符集布尔型boolean1 字节true 或 false 所有的基本数据类型的大小(所占用的字节数)都已明确规定,在各种不同的平台上保持不变,这一特性有助于提高 Java 程序的可移植性。
(2) 引用数据类型建立在基本数据类型的基础上,包括数组、类和接口。引用数据类型是由用户自定义,用来限制其他数据的类型。另外,Java 语言中不支持 C++ 中的指针类型、结构类型、联合类型和枚举类型。
Java 数据类型的结构如图 1 所示:
图1 Java数据类型结构图
Java 是一种强制类型的语言,所有的变量都必须先明确定义其数据类型,然后才能使用。Java 中所有的变量、表达式和值都必须有自己的类型,没有“无类型”变量这样的概念。
基本数据类型又可分为 4 大类,即整数类型(包括 byte、short,int 和 long)、浮点类型(包括 float 和 double)、布尔类型和字符类型(char)。
整数类型 Java 定义了 4 种整数类型变量:字节型(byte)、短整型(short)、整型(int)和长整型(long)。这些都是有符号的值,正数或负数。
字节型(byte)
byte 类型是最小的整数类型。当用户从网络或文件中处理数据流时,或者处理可能与 Java 的其他内置类型不直接兼容的未加工的二进制数据时,该类型非常有用。
短整型(short)
可以,打个比方:单例是一个厨房,线程是厨师,方法是挂在墙上的菜谱,现在多个厨师在一个厨房里照着墙上的菜谱做菜,并不冲突。什么时候冲突?厨房里只有一个水龙头(单例变量等),厨师都要去接水,这个时候就会发生排队阻塞。多例多线程也就是多个厨房多个厨师,每个厨师配一个厨房,所以为什么要有单例模式,不用想也明白吧!
架构师或者项目经理可能经常需要绘制UML类图,但是很多人却绘制的很不规范,其实UML针对Java是有专业规范存在的,下面开始详解
一.类属性描述: 在UML类图中,类使用包含类名、属性(field) 和方法(method) 且带有分割线的矩形来表示,比如下图表示一个NoticeAction类,它包含notice和noticeService这2个属性,以及saveNotice()等方法。
那么属性/方法名称前加的加号和减号是什么意思呢?它们表示了这个属性或方法的可见性,UML类图中表示可见性的符号有三种:
· + :表示public
· - :表示private
· #:表示protected(friendly也归入这类)
二.类与类之间关系描述: 1.单向关联关系 我们可以看到,在UML类图中单向关联用一个带箭头的直线表示。上图表示每个顾客都有一个地址,这通过让Customer类持有一个类型为Address的成员变量类实现。
2.双向关联关系 从上图中我们很容易看出,所谓的双向关联就是双方各自持有对方类型的成员变量。在UML类图中,双向关联用一个不带箭头的直线表示。上图中在Customer类中维护一个Product[]数组,表示一个顾客购买了那些产品;在Product类中维护一个Customer类型的成员变量表示这个产品被哪个顾客所购买。
3.自关联关系 自关联在UML类图中用一个带有箭头且指向自身的直线表示。上图的意思就是Node类包含类型为Node的成员变量,也就是“自己包含自己”。
4.聚合关系 上图中的Car类与Engine类就是聚合关系(Car类中包含一个Engine类型的成员变量)。由上图我们可以看到,UML中聚合关系用带空心菱形和箭头的直线表示。聚合关系强调是“整体”包含“部分”,但是“部分”可以脱离“整体”而单独存在。比如上图中汽车包含了发动机,而发动机脱离了汽车也能单独存在。
5.组合关系 组合关系与聚合关系见得最大不同在于:这里的“部分”脱离了“整体”便不复存在,显然嘴是头的一部分且不能脱离了头而单独存在。在UML类图中,组合关系用一个带实心菱形和箭头的直线表示。
6.依赖关系 从上图我们可以看到,Driver的drive方法只有传入了一个Car对象才能发挥作用,因此我们说Driver类依赖于Car类。在UML类图中,依赖关系用一条带有箭头的虚线表示。
7.继承关系 继承关系对应的是extend关键字,在UML类图中用带空心三角形的直线表示,如下图所示中,Student类与Teacher类继承了Person类。
8.实现关系 这种关系对应implement关键字,在UML类图中用带空心三角形的虚线表示。如下图中,Car类与Ship类都实现了Vehicle接口。
NVIDIA显卡驱动&Cuda安装教程 材料准备 **正确安装的GPU显卡: NVIDIA显卡驱动离线安装包:** 安装流程 1.开始安装CUDA!同样在官网上下载离线安装包(以.run为后):
链接:(https://developer.nvidia.com/cuda-downloads)
2.登入系统,使用组合键【Ctrl】+ 【Alt】+【F2】进入命令行,并使用用户登录
3.再次确保关闭X服务,
命令:service lightdm stop 验证:无报错即可
4.赋予Cuda安装包可执行权限:
命令:chmod +x cuda_8.0.44_linux.run (以实际包名为准)
验证:通过ls 命令查看,包名高亮显示即可
5.执行Cuda安装指令:
命令:./cuda_8.0.44_linux.run --no-opengl-libs
(集显需加opengl相关参数) 验证:出现一些用户许可证信息
6.直接按**【Q】键,并输入【accept】**回车
7.配置cuda环境变量: 命令:vim /etc/environment
之后按【i】键,直到屏幕左下角出现 【INSERT 】字样, 使用 【←】 【→】并更改其文本内容为:
PATH=“/usr/local/cuda/bin:/usr/local/sbin:/usr/local/bin:/usr/sblocal/games”
LANG="en_US.UTF-8"
完成更改之后,按【Esc】键盘,发现【INSERT 】消失了!
然后依次输入【:wq】(出现在左下角)再回车以保存文件.
命令:vim /etc/ld.so.conf.d/cuda.conf
之后按【i】键,直到屏幕左下角出现 【INSERT 】字样, 使用【←】【→】并添加如下文本内容:
/usr/local/cuda/lib64
完成更改之后,按【Esc】键盘,发现【INSERT 】消失了!然后依次输入【:wq】 (出现在左下角)再回车以保存文件.
命令:ldconfig (加载环境变量)
验证:输入 nvcc -V (可以看到cuda版本)
8.在NVIDIA官网上下载对应型号的显卡驱动离线包(以.run为后):
链接:(https://www.nvidia.cn/Download/Find.aspx?lang=cn)
9.确认关闭系统自带的X服务,
命令: service lightdm stop
验证:无报错即可
10.进入下载目录,找到驱动离线包,并给予可安装的权限:
命令:chmod +x NVIDIALinuxx86_64378.13.linux.run
问题描述:
本地文件都能创建,就是不能clone
终端名称出现乱码
原因:
计算机名称为 中文名称
解决途径:
修改计算机名称,设置为英文名称
问题解决
转载于:https://www.cnblogs.com/TNT-c/p/10675895.html
flutter BoxDecoration源码常用属性如下
const BoxDecoration({
this.color, // 底色
this.image, // 背景图
this.border, // 边框颜色
this.borderRadius, // 圆角大小
this.boxShadow, // 阴影
this.gradient, // 渐变
this.shape = BoxShape.rectangle, // 形状
})
请看效果:
请看代码:
Container(
margin: EdgeInsets.only(left: 15,top: 15),
padding: EdgeInsets.only(left: 12, right: 12, top: 5, bottom: 5),
decoration: BoxDecoration(
border: Border.all(color: Colors.red, width: 1),//边框
borderRadius: BorderRadius.all(//圆角
Radius.circular(20.0),
),
),
child: Text(
‘全边框全圆角’,
style: TextStyle(
color: Colors.red,
),
),
),
Container(
margin: EdgeInsets.only(left: 15,top: 15),
一、Zookeeper节点的4种状态:
LEADING:说明此节点已经是leader节点,处于领导者地位的状态,差不多就是一般集群中的master。但在zookeeper中,只有leader才有写权限,其他节点(FOLLOWING)是没有写权限的,可以读
LOOKING:选举中,正在寻找leader,即将进入leader选举流程中
FOLLOWING:跟随者,表示当前集群中的leader已经选举出来了,主要具备以下几个功能点 向leader发送请求(PING消息、REQUEST消息、ACK消息、REVALIDATE消息)
接收leader消息并进行处理;
接收client发送过来的请求,如果为写请求,会发送给Leader进行投票处理,然后返回client结果。
OBSERVING:OBSERVING和FOLLOWING差不多,但不参加投票和选举,接受leader选举后的结果
二、Zookeeper中Leader选举
初始化
当启动初始化集群的时候,server1的myid为1,zxid为0 server2的myid为2,zxid同样是0,以此类推。此种情况下zxid都是为0。先比较zxid,再比较myid
服务器1启动,给自己投票,然后发投票信息,由于其它机器还没有启动所以它收不到反馈信息,服务器1的状态一直属于Looking(选举状态)。
服务器2启动,给自己投票,同时与之前启动的服务器1交换结果,由于服务器2的myid大所以服务器2胜出,但此时投票数没有大于半数,所以两个服务器的状态依然是LOOKING。
服务器3启动,给自己投票,同时与之前启动的服务器1,2交换信息,由于服务器3的myid最大所以服务器3胜出,此时投票数正好大于半数,所以服务器3成为领导者,服务器1,2成为小弟。
服务器4启动,给自己投票,同时与之前启动的服务器1,2,3交换信息,尽管服务器4的myid大,但之前服务器3已经胜出,所以服务器4只能成为小弟。
服务器5启动,后面的逻辑同服务器4成为小弟
当选举机器过半的时候,已经选举出leader后,后面的就跟随已经选出的leader,所以4和5跟随成为leader的server3
所以,在初始化的时候,一般到过半的机器数的时候谁的myid最大一般就是leader
运行期间
按照上述初始化的情况,server3成为了leader,在运行期间处于leader的server3挂了,那么非Observer服务器server1、server2、server4、server5会将自己的节点状态变为LOOKING状态
1、开始进行leader选举。现在选举同样是根据myid和zxid来进行
2、首先每个server都会给自己投一票竞选leader。假设server1的zxid为123,server2的zxid为124,server4的zxid为169,server5的zxid为188
3、同样先是比较zxid再比较,server1、server2、server4比较server4根据优先条件选举为leader。然后server5还是跟随server4,即使server5的zxid最大,但是当选举到server4的时候,机器数已经过半。不再进行选举,跟随已经选举的leader
zookeeper集群为保证数据的一致性所有的操作都是由leader完成,之后再由leader同步给follower。重点就在这儿,zookeeper并不会确保所有节点都同步完数据,只要有大多数节点(即n/2+1)同步成功即可。
咱们假设有一个写操作成功那么现在数据只存在于节点leader,之后leader再同步给其他follower。这时候宕掉3个机器,已经过半的机器无法进行投票选举,剩余2台不足过半,无法选举=无法提供任何服务。再启动一个机器恢复服务。所以宕掉的机器不要过半,过半就会导致无法正常服务。
在leader选举的时候会有30s-120s的过程,在这期间也是无法提供服务的。如果用zookeeper要作为服务发现是个弊端,基本无法忍受,zookeeper本身是一个CP系统,保证数据的一致性,在恢复的时候再提供服务,并没有多好高可用的方案。如果leader发生故障选举时无法提供服务发现对一个大型应用来说可能是致命的。它可以为同在一个分布式系统中的其他服务提供:统一命名服务、配置管理、分布式锁服务、集群管理等功能)是个伟大的开源项目,很成熟
选举比对值分析
zxid:zxid(64位)又称事务id,它如果最大,那么会成为leader。它越大,数据也越新。
myid:服务器id,又称sid。它如果越大,在leader选举机制中权重越大。
epoch:属于zxid里的高32位,每一轮leader选举投票,它都会递增
QuorunCnxManager:网络IO,通信
每台服务器再启动的过程中,会启动一个QuorCnxManager,负责各个服务器之间底层的通信,这是实现leader选举的必要前提。
QuorCnxManager中维护了一系列的队列,用来保存接受到的,待发送的消息以及消息的发送器
recvQueue:消息接受队列,用于存放那些从其他服务器接受到的消息
queueSendMap:消息发送队列,用于保存那些待发送的消息,按照SID进行分组
senderWorkerMap:发送器集合,每个SenderWorker消息发送器,都对应一台远程Zookeeper服务器,负责消息的发送
lastMessageSent:最近发送过的消息,为每个SID保留最近发送过的一个消息
建立连接:为了能够相互投票,zookeeper集群中的所有机器都需要俩俩建立起网络连接,QuorumCnxManager在启动的时候会创建一个ServerSocket来监听leader悬崖边还有的通信端口,默认3888。开启监听后, zookeeper能够不断的接受来自其他服务器的创建连接请求,在接受其他服务器的TCP请求时会进行处理,为了避免俩台机器之间重复的创建TCP连接,zookeeper只允许SID大的服务器主动连接其他服务器,否则断开连接,在接收到创建连接请求后,服务器通过对比自己和远程服务器的sid值来判断时候接受连接请求,一旦建立连接,就会根据远程服务器的sid创建对应的消息发送器SendWorker和消息接收RecvWorker,
消息接受与发送
消息接受:由消息接收器RecvWorker负责,由于zookeeper为每隔远程服务器都分配一个单独的RecvWorker,因此,每个RecvWorker只需要不断地从这个连接中读取消息,并且将其消息保存到recvQueue队列中
消息发送:由于zookeeper中为每个远程服务器都分配一个单独的SendWorker,因此,每个SendWorker只需要不断地从对应的消息发送队列中获取出一个消息发送即可,同时将这个消息放入到lastMessageSent中,在SendWorker中,一旦zookeeper发现针对当前服务器的消息发送队列为空,那么此时就需要从lastMessageSent中取出一个最近发送过的消息来进行再次发送,这是为了解决接收方在消息接受前或者接受到消息后服务器挂了,导致消息尚未被处理
FaseLeadElection:选举算法核心
选举轮次:Zookeeper服务器Leader选举的轮次,即logicalclock。
sendQueue:选票发送队列,用于保存待发送的选票
recvQueue:选票接受队列,用于保存接受到的外部服务器的投票
WorkerReceiver:选票接收器,会不断地从QuorumCnxMangager中获取其他服务器发送过来的选举消息,并将其保存到recvQueue中,在选票接受的过程中,如果发现该外部选票的选举轮次小于当前服务器的,那么忽略外投票,同时立即发送自己的内部投票
workerSender:选票发送器,不断地从sendqueue中获取待发送的选票,并将其传递到底层QuorumCnxManager中
1、自增选举轮次。Zookeeper规定所有有效的投票都必须在同一轮次中,在开始新一轮投票时,会首先对logicalclock进行自增操作。
2、初始化选票。在开始进行新一轮投票之前,每个服务器都会初始化自身的选票,并且在初始化阶段,每台服务器都会将自己推举为Leader。
3、发送初始化选票。完成选票的初始化后,服务器就会发起第一次投票。Zookeeper会将刚刚初始化好的选票放入sendqueue中,由发送器WorkerSender负责发送出去。
4、接收外部投票。每台服务器会不断地从recvqueue队列中获取外部选票。如果服务器发现无法获取到任何外部投票,那么就会立即确认自己是否和集群中其他服务器保持着有效的连接,如果没有连接,则马上建立连接,如果已经建立了连接,则再次发送自己当前的内部投票。
5、判断选举轮次。在发送完初始化选票之后,接着开始处理外部投票。在处理外部投票时,会根据选举轮次来进行不同的处理。
外部投票的选举轮次大于内部投票。若服务器自身的选举轮次落后于该外部投票对应服务器的选举轮次,那么就会立即更新自己的选举轮次(logicalclock),并且清空所有已经收到的投票,然后使用初始化的投票来进行PK以确定是否变更内部投票。最终再将内部投票发送出去。
外部投票的选举轮次小于内部投票。若服务器接收的外选票的选举轮次落后于自身的选举轮次,那么Zookeeper就会直接忽略该外部投票,不做任何处理,并返回步骤4。
外部投票的选举轮次等于内部投票。此时可以开始进行选票PK。
6、选票PK。在进行选票PK时,符合任意一个条件就需要变更投票。
若外部投票中推举的Leader服务器的选举轮次大于内部投票,那么需要变更投票。
若选举轮次一致,那么就对比两者的ZXID,若外部投票的ZXID大,那么需要变更投票。
若两者的ZXID一致,那么就对比两者的SID,若外部投票的SID大,那么就需要变更投票。
7、变更投票。经过PK后,若确定了外部投票优于内部投票,那么就变更投票,即使用外部投票的选票信息来覆盖内部投票,变更完成后,再次将这个变更后的内部投票发送出去。
8、选票归档。无论是否变更了投票,都会将刚刚收到的那份外部投票放入选票集合recvset中进行归档。recvset用于记录当前服务器在本轮次的Leader选举中收到的所有外部投票(按照服务队的SID区别,如{(1, vote1), (2, vote2)...})。
9、统计投票。完成选票归档后,就可以开始统计投票,统计投票是为了统计集群中是否已经有过半的服务器认可了当前的内部投票,如果确定已经有过半服务器认可了该投票,则终止投票。否则返回步骤4。
10、更新服务器状态。若已经确定可以终止投票,那么就开始更新服务器状态,服务器首选判断当前被过半服务器认可的投票所对应的Leader服务器是否是自己,若是自己,则将自己的服务器状态更新为LEADING,若不是,则根据具体情况来确定自己是FOLLOWING或是OBSERVING
【二】zookeeper中watcher机制解析
目录
综述
ResultType
返回常见类型:(类似于int或者Integer)
返回Map
返回一个对象
ResultMap
基本使用
id和result
高级使用
Constructor元素
Association
Collection
discriminator
综述 MyBatis中在查询进行select映射的时候,返回类型可以用resultType,也可以用resultMap,resultType是直接表示返回类型的,而resultMap则是对外部ResultMap的引用,但是resultType跟resultMap不能同时存在。
在MyBatis进行查询映射时,其实查询出来的每一个属性都是放在一个对应的Map里面的,其中键是属性名,值则是其对应的值。
①当提供的返回类型属性是resultType时,MyBatis会将Map里面的键值对取出赋给resultType所指定的对象对应的属性。所以其实MyBatis的每一个查询映射的返回类型都是ResultMap,只是当提供的返回类型属性是resultType的时候,MyBatis对自动的给把对应的值赋给resultType所指定对象的属性。
②当提供的返回类型是resultMap时,因为Map不能很好表示领域模型,就需要自己再进一步的把它转化为对应的对象,这常常在复杂查询中很有作用。
ResultType resultType可以直接返回给出的返回值类型,比如String、int、Map,等等,其中返回List也是将返回类型定义为Map,然后mybatis会自动将这些map放在一个List中,resultType还可以是一个对象,举例如下:
返回常见类型:(类似于int或者Integer) <select id="getLogCount" resultType="int"> select COUNT(*) from AttLog where attTime = #{attTime} and userId = #{userId}; </select> 返回Map <select id="getDeviceInfoByDeviceId" resultType="Map"> select userCount as usercount, fingerCount as fingercount, faceCount as facecount, attRecordCount as recordcount, lastOnline, state as status from DeviceInfo where deviceId = #{deviceId} and tb_isDelete = 0; </select> 返回一个对象或者一个list(list里面是resultType的类型)
首先在桌面上创建一个文本文档
然后打开然后将下面的代码复制粘贴过去:
<!DOCTYPE html> <html> <head> <title>兴趣小组</title> </head> <body> <canvas id="canvas"></canvas> <style type="text/css"> body{margin: 0; padding: 0; overflow: hidden;} </style> <script type="text/javascript"> var canvas = document.getElementById('canvas'); var ctx = canvas.getContext('2d'); canvas.height = window.innerHeight; canvas.width = window.innerWidth; var texts = '阿喆最帅'.split(''); var fontSize = 16; var columns = canvas.width/fontSize; // 用于计算输出文字时坐标,所以长度即为列数 var drops = []; //初始值 for(var x = 0; x < columns; x++){ drops[x] = 1; } function draw(){ //让背景逐渐由透明到不透明 ctx.
单点登录原理 一、单系统登录机制
1、http无状态协议
web应用采用browser/server架构,http作为通信协议。http是无状态协议,浏览器的每一次请求,服务器会独立处理,不与之前或之后的请求产生关联,这个过程用下图说明,三次请求/响应对之间没有任何联系
但这也同时意味着,任何用户都能通过浏览器访问服务器资源,如果想保护服务器的某些资源,必须限制浏览器请求;要限制浏览器请求,必须鉴别浏览器请求,响应合法请求,忽略非法请求;要鉴别浏览器请求,必须清楚浏览器请求状态。既然http协议无状态,那就让服务器和浏览器共同维护一个状态吧!这就是会话机制
2、会话机制
浏览器第一次请求服务器,服务器创建一个会话,并将会话的id作为响应的一部分发送给浏览器,浏览器存储会话id,并在后续第二次和第三次请求中带上会话id,服务器取得请求中的会话id就知道是不是同一个用户了,这个过程用下图说明,后续请求与第一次请求产生了关联
服务器在内存中保存会话对象,浏览器怎么保存会话id呢?你可能会想到两种方式
请求参数
cookie
将会话id作为每一个请求的参数,服务器接收请求自然能解析参数获得会话id,并借此判断是否来自同一会话,很明显,这种方式不靠谱。那就浏览器自己来维护这个会话id吧,每次发送http请求时浏览器自动发送会话id,cookie机制正好用来做这件事。cookie是浏览器用来存储少量数据的一种机制,数据以”key/value“形式存储,浏览器发送http请求时自动附带cookie信息
tomcat会话机制当然也实现了cookie,访问tomcat服务器时,浏览器中可以看到一个名为“JSESSIONID”的cookie,这就是tomcat会话机制维护的会话id,使用了cookie的请求响应过程如下图
3、登录状态
有了会话机制,登录状态就好明白了,我们假设浏览器第一次请求服务器需要输入用户名与密码验证身份,服务器拿到用户名密码去数据库比对,正确的话说明当前持有这个会话的用户是合法用户,应该将这个会话标记为“已授权”或者“已登录”等等之类的状态,既然是会话的状态,自然要保存在会话对象中,tomcat在会话对象中设置登录状态如下
HttpSession session = request.getSession(); session.setAttribute("isLogin", true); 用户再次访问时,tomcat在会话对象中查看登录状态
HttpSession session = request.getSession(); session.getAttribute("isLogin"); 实现了登录状态的浏览器请求服务器模型如下图描述
每次请求受保护资源时都会检查会话对象中的登录状态,只有 isLogin=true 的会话才能访问,登录机制因此而实现。
二、多系统的复杂性
web系统早已从久远的单系统发展成为如今由多系统组成的应用群,面对如此众多的系统,用户难道要一个一个登录、然后一个一个注销吗?就像下图描述的这样
web系统由单系统发展成多系统组成的应用群,复杂性应该由系统内部承担,而不是用户。无论web系统内部多么复杂,对用户而言,都是一个统一的整体,也就是说,用户访问web系统的整个应用群与访问单个系统一样,登录/注销只要一次就够了
虽然单系统的登录解决方案很完美,但对于多系统应用群已经不再适用了,为什么呢?
单系统登录解决方案的核心是cookie,cookie携带会话id在浏览器与服务器之间维护会话状态。但cookie是有限制的,这个限制就是cookie的域(通常对应网站的域名),浏览器发送http请求时会自动携带与该域匹配的cookie,而不是所有cookie
既然这样,为什么不将web应用群中所有子系统的域名统一在一个顶级域名下,例如“*.baidu.com”,然后将它们的cookie域设置为“baidu.com”,这种做法理论上是可以的,甚至早期很多多系统登录就采用这种同域名共享cookie的方式。
然而,可行并不代表好,共享cookie的方式存在众多局限。首先,应用群域名得统一;其次,应用群各系统使用的技术(至少是web服务器)要相同,不然cookie的key值(tomcat为JSESSIONID)不同,无法维持会话,共享cookie的方式是无法实现跨语言技术平台登录的,比如java、php、.net系统之间;第三,cookie本身不安全。
因此,我们需要一种全新的登录方式来实现多系统应用群的登录,这就是单点登录
三、单点登录
什么是单点登录?单点登录全称Single Sign On(以下简称SSO),是指在多系统应用群中登录一个系统,便可在其他所有系统中得到授权而无需再次登录,包括单点登录与单点注销两部分
1、登录
相比于单系统登录,sso需要一个独立的认证中心,只有认证中心能接受用户的用户名密码等安全信息,其他系统不提供登录入口,只接受认证中心的间接授权。间接授权通过令牌实现,sso认证中心验证用户的用户名密码没问题,创建授权令牌,在接下来的跳转过程中,授权令牌作为参数发送给各个子系统,子系统拿到令牌,即得到了授权,可以借此创建局部会话,局部会话登录方式与单系统的登录方式相同。这个过程,也就是单点登录的原理,用下图说明
下面对上图简要描述
用户访问系统1的受保护资源,系统1发现用户未登录,跳转至sso认证中心,并将自己的地址作为参数
sso认证中心发现用户未登录,将用户引导至登录页面
用户输入用户名密码提交登录申请
sso认证中心校验用户信息,创建用户与sso认证中心之间的会话,称为全局会话,同时创建授权令牌
sso认证中心带着令牌跳转会最初的请求地址(系统1)
系统1拿到令牌,去sso认证中心校验令牌是否有效
sso认证中心校验令牌,返回有效,注册系统1
系统1使用该令牌创建与用户的会话,称为局部会话,返回受保护资源
用户访问系统2的受保护资源
系统2发现用户未登录,跳转至sso认证中心,并将自己的地址作为参数
sso认证中心发现用户已登录,跳转回系统2的地址,并附上令牌
系统2拿到令牌,去sso认证中心校验令牌是否有效
sso认证中心校验令牌,返回有效,注册系统2
系统2使用该令牌创建与用户的局部会话,返回受保护资源
用户登录成功之后,会与sso认证中心及各个子系统建立会话,用户与sso认证中心建立的会话称为全局会话,用户与各个子系统建立的会话称为局部会话,局部会话建立之后,用户访问子系统受保护资源将不再通过sso认证中心,全局会话与局部会话有如下约束关系
局部会话存在,全局会话一定存在
全局会话存在,局部会话不一定存在
全局会话销毁,局部会话必须销毁
你可以通过博客园、百度、csdn、淘宝等网站的登录过程加深对单点登录的理解,注意观察登录过程中的跳转url与参数
2、注销
单点登录自然也要单点注销,在一个子系统中注销,所有子系统的会话都将被销毁,用下面的图来说明
sso认证中心一直监听全局会话的状态,一旦全局会话销毁,监听器将通知所有注册系统执行注销操作
下面对上图简要说明
用户向系统1发起注销请求
1、删除软件及其配置文件
sudo apt purge 软件名 2、删除没用的依赖包
sudo apt autoremove 3、此时dpkg的列表中有“rc”状态的软件包,可以执行如下命令做最后清理
dpkg -l |grep ^rc|awk '{print $2}' |sudo xargs dpkg -P 软件名 4、更新本地缓存
sudo apt update 5、sudo apt 默认下载位置及安装位置
下载的软件的存放位置:/var/cache/apt/archives 安装后软件的默认位置:/usr/share 可执行文件位置:/usr/bin 配置文件位置:/etc lib文件位置:/usr/lib