首页 文章 精选 留言 我的

精选列表

搜索[学习],共10000篇文章
优秀的个人博客,低调大师

微服务学习之thrift介绍安装

最近在做一个docker+Kubernetes微服务容器化实战项目,项目中用到了很多不熟悉的组件,也遇到了挺多的问题。项目结构是这样的: 在做信息服务的时候需要安装thrift组件,遇到了点儿问题,特此记录下过程。 首先来说一下thrift: 什么是thrift? 简单来说,是Facebook公布的一款开源跨语言的RPC框架. 什么是RPC框架? RPC全称为Remote Procedure Call,意为远程过程调用,常用的rpc框架有thrift、dubbo、grpc、motan等。 假设有两台服务器A,B。A服务器上部署着一个应用a,B服务器上部署着一个应用b,现在a希望能够调用b应用的某个函数(方法),但是二者不在同一个进程内,不能直接调用,就需要通过网络传输,在AB服务器之间建一条网络传输通道,a把参数传过去,b接收到参数调用自己的方法,得到结果,再通过网络传回给a,简单讲就是A通过网络来调用B的过程.这个过程要涉及的东西很多,比如多线程,Socket,序列化反序列化,网络I/O,很复杂,于是牛掰的程序员把这些封装起来做成一套框架,供大家使用,就是RPC框架。 thrift的跨语言特型 thrift通过一个中间语言IDL(接口定义语言)来定义RPC的数据类型和接口,这些内容写在以.thrift结尾的文件中,然后通过特殊的编译器来生成不同语言的代码,以满足不同需要的开发者,比如java开发者,就可以生成java代码,c++开发者可以生成c++代码,生成的代码中不但包含目标语言的接口定义,方法,数据类型,还包含有RPC协议层和传输层的实现代码. thrift的协议栈结构 thrift是一种c/s的架构体系.在最上层是用户自行实现的业务逻辑代码.第二层是由thrift编译器自动生成的代码,主要用于结构化数据的解析,发送和接收。TServer主要任务是高效的接受客户端请求,并将请求转发给Processor处理。Processor负责对客户端的请求做出响应,包括RPC请求转发,调用参数解析和用户逻辑调用,返回值写回等处理。从TProtocol以下部分是thirft的传输协议和底层I/O通信。TProtocol是用于数据类型解析的,将结构化数据转化为字节流给TTransport进行传输。TTransport是与底层数据传输密切相关的传输层,负责以字节流方式接收和发送消息体,不关注是什么数据类型。底层IO负责实际的数据传输,包括socket、文件和压缩数据流等。 下面介在Centos7.2上安装thrift: Thirft下载安装: 这里安装源码包,即后缀是tar.gz 或者.tgz包: wget http://mirrors.tuna.tsinghua.edu.cn/apache/thrift/0.11.0/thrift-0.11.0.tar.gz 查看README和INSTALL文件,根据README和INSTALL文件,查看thrift安装说明以及thrift依赖的软件包,然后解压源码包: tar -zxf thrift-0.11.0.tar.gz 源码包第一步需要先执行以下命令,tarball(即后缀是.tar的包)不需要: ./bootstrap.sh 然后直接运行软件根目录下的configure脚本 ./configure 查看回显,显示java和python、go编译yes,其他的no编译。 因为我只需要编译java和python、go,其他的可以忽略不管。 这里需要提前安装go的环境,可以参考:https://www.jianshu.com/p/b2222fc04f47 这里有一点需要注意,本来已经安装了jdk,用java -version看到的版本1.8的jdk。 但是./configure执行完java还是no。然后查看configure文件里有如下的代码: 大概意思是thrift编译java需要jdk和ant,然后再执行ant -version发现确实没有安装ant。那就安装ant呗: 安装ant: 这里顺便安了一下maven。 wget http://repos.fedorapeople.org/repos/dchen/apache-maven/epel-apache-maven.repo -O /etc/yum.repos.d/epel-apache-maven.repo yum -y install apache-maven yum -y install ant ant安装好了: 如果你也遇到./configure执行完java后面是no的情况,你就得首先检查jdk和ant了。 下面继续执行安装thrift的步骤: #使用make命令进行编译 make #使用make install命令进行安装 make install 最后验证: 输入thrift -version命令,查看输出信息判断thrift是否安装成功 $ thrift -version Thrift version 0.11.0 如上回显,说明thrift安装成功,至此,你可以利用thrift进行开发实现。 windows安装请参考: https://www.jianshu.com/p/42e4b5919434 原文发布时间为:2018-09-19 本文作者:小碗汤 本文来自云栖社区合作伙伴“我的小碗汤”,了解相关信息可以关注“我的小碗汤”。

优秀的个人博客,低调大师

Python Flask学习知识点(六)

timg (10).jpg image.png Flask多线程机制 发送请求和服务器处理请求的线程之间的关系? 客户端发送十个请求,Flask开启多少个线程处理请求?其实是由web服务器开启的多线程。 如果要上生产线,一般不会用Flask自带的web server。 对于一个web网站而言,必须有承受并发的能力。 单线程: 如果是单线程,只有当一个请求处理完成之后,才会处理下一个请求,所以这时候看起来就像是要排队一样 单线程下,当一个请求进来之后,Flask会实例化一个Request对象,用这个对象来装载请求信息,这时候只有一个实例化的request,当第一个请求结束后,Flask会再实例化一个Request对象来装载第二个请求,然后request变量又指向当前请求,那么这样众多用户就不会混乱。 多线程: werkzeug库下local模块下的Local对象,Flask就是用Local来做线程隔离的。 Local对象的本质就是用线程的id号作为字典的键这种基本原理实现的。 Flask中线程隔离栈LocalStack LocalStack 是实现线程隔离的栈结构,把Local封装了。 LocalStack需要使用push方法 LocalStack: Local特性 Stack特性:栈结构,只能取栈顶元素,先进后出 使用线程隔离的意义在于:使当前线程能够正确引用到他自己做创建的对象,而不是引用到其他线程做创建的对象 线程隔离对象 LocalStack 问:Flask使用LocalStack为了隔离哪个对象? 答:被线程隔离对象 RequestContext AppContext Flask核心对象全局只有一个。 看一段测试代码,测试下LocalStack线程隔离: import threading from werkzeug.local import LocalStack stack = LocalStack() stack.push('a') print('主线程top:', stack.top) def child(): print("子线程:", stack.top) stack.push('b') print('子线程:', stack.top) t = threading.Thread(target=child, name='demo') t.start() print('主线程top:', stack.top) 打印结果: 主线程top: a 子线程: None 子线程: b 主线程top: a 还有哪些是被线程隔离的对象? image.png 以上都是被线程隔离的对象。 ViewModel的基本概念 很多时候,我们的数据在提交给页面的时候需要做一些处理或者裁剪,这时就需要ViewModel层来做这件事,如图: image.png viewModel作用 裁剪 修饰 合并 app文件夹下新建文件夹view_models,这里就可以处理数据的裁剪、修饰、合并。 单页面与网站的区别 一个网站页面由哪几部分构成? JS CSS(静态文件) HTML模板 数据 网站 一般来讲,服务器端把数据返回到HTML模板中,称之为服务器渲染,然后返回到浏览器给用户,此时,客户端浏览器网页中的JS CSS起作用再一次发起请求渲染页面,这是一个完整的页面就处理完了。 image.png 单页面 浏览器去请求一个静态的html,html返回到客户端浏览器后,html里边的js css 发起请求,来填充数据。 image.png 单页面和普通网站有什么区别: 区别的第一种说法: 对于多页面普通网站而言,大多数情况下它的数据渲染或者说模板填充都是在服务器端进行的, 而对于单页面来说,他数据的渲染是在客户端进行的。 区别的第二种说法: 单页面业务逻辑也就是说数据运算主要集中在客户端用js去操作,而普通网站而言,绝大多数业务逻辑是在服务器,也就是视图函数中去进行的。

优秀的个人博客,低调大师

Python Flask学习知识点(四)

timg (8).jpg 上节讲到,视图函数可以以接受参数的形式来获取传入后台的参数,但是往往我们需要对参数进行校验,比如说请求一个10条数据,那么page=10,但是如果某些比较皮的小盆友传入一个page=10000,那服务器也要去数据库查询10000条记录返回吗?显然不能那么做,所以这就涉及到在Flask中做参数检查的工作。 WTForms参数验证 首先,安装这个第三方的插件wtformspip install wtforms 我们都知道,在web应用中,分层这个概念非常重要,MVC模式其实可以理解为就是分层,这里我们引入验证层这个概念。无论写任何的web应用,验证层都是非常重要的概念。 所以我们所有的参数校验工作,都不会直接写到视图函数中,而是都会放到验证层去做。 在app文件夹下新建forms文件夹,并新建book.py文件: image.png 编写book.py: from wtforms.validators import Length, NumberRange, DataRequired() from wtforms import Form, StringField, IntegerField class SearchForm(Form): q = StringField(validators=[DataRequired(), Length(min=1, max=30)]) page = IntegerField(validators=[NumberRange(min=1, max=99)], default=1) 解释这段代码: 使用wtforms提供的StringField对象来定义参数q的类变量 使用wtforms提供的IntegerField对象来定义参数page的类变量 wtforms内置了很多的验证对象来帮助我们快速的完成对参数的验证,而不需要手动编写验证函数。当然了,我们也可以自定义验证对象。 为了验证q参数,可以使用内置的验证对象:Length,验证q参数的长度,validators接受一个list,可以传入多个验证对象,这里我们验证q参数传入Length验证对象。 同样,验证page参数也是同样的原理,这里使用的三个内置的验证对象(NumberRange,Length,DataRequired),可以查阅文档了解详细的用法。 编写完验证层代码,在视图函数中调用,更改web文件夹下book.py: from flask import jsonify, request from flaskDemo.app.forms.book import SearchForm from . import web @web.route('/hello') def search(): form = SearchForm(request.args) if form.validate(): q = form.q.data page = form.page.data result = {"name": q, "valus": page} return jsonify(result) return jsonify({"error": 0}) 解释代码: 首先导入SearchForm并实例化为form对象,并且传入参数request.args,通过form.validate()的返回值判断是否符合验证器规定的结果。 这里讲一点,使用page = form.q.data这种方式获取q和page的值,而不直接使用q,是因为我们之前在验证层SearchForm中定义了page参数如果为空,使用1为默认值,所以我们要从form.q.data 这种拿到page参数。 看下效果,运行run.py启动,在浏览器中输入符合规则的urlhttp://127.0.0.1:8000/hello?q=demo&page=1 再输入不符合规则的urlhttp://127.0.0.1:8000/hello?q=demo&page=100 返回结果: image.png image.png 一个是成功,一个是失败。 因为之前我们在验证层中定义了page的最大值为99,所以如果输入100,就会返回错误。 再试一下不输入page参数:http://127.0.0.1:8000/hello?q=demo image.png 还是可以返回默认值。 继续,看下图: image.png debug调试过程中,看下form实例中的一个errors,这个errors就是当验证不通过时,wtforms给我们的一个错误提示,在一般情况下,如果参数验证不通过时是会抛出一个异常的,但是使用wtforms,它会把错误提示放到errors属性中而不抛出异常。 更改代码: def search(): form = SearchForm(request.args) if form.validate(): q = form.q.data page = form.page.data result = {"name": q, "valus": page} return jsonify(result) return jsonify({"error": form.errors}) 这里改为return jsonify({"error": form.errors})。 再运行代码输入URLhttp://127.0.0.1:8000/hello?q=demo&page=100得到如下: image.png 可见wtforms告诉我们为什么验证不通过。 这里也可以在SearchForm自定义返回的错误信息,只需更改为: class SearchForm(Form): q = StringField(validators=[DataRequired(), Length(min=1, max=30)]) page = IntegerField(validators=[NumberRange(min=1, max=99, message="传入的参数不符合要求")], default=1) 再运行代码: image.png 拆分配置文件 之前我们一直是把配置参数全部放到config.py中,但是这样的话会有一个问题,那就是如果我们把比较私密的配置(例如数据库地址以及密码)和普通的参数放在一起,势必会有安全风险,比如把代码传到了git上,所以最好把配置文件拆分为两种不同级别的,这里我们把config.py拆分为secure.py和setting.py,并且全部放到app文件夹下。 secure.py 用来存放数据库密码、账号还有我们后边提到的flask app key ,这样比较机密的信息,单独放配置文件中,还有就是开发环境和生产环境中的不同设置,比如 debug=True。 setting.py 用来存放不涉及到机密的,生产和开发环境一样的配置。 更改之前的代码,app文件夹下的__init__.py: def create_app(): app = Flask(__name__) app.config.from_object("app.setting") app.config.from_object("app.secure") register_blueprint(app) return app current_app 在Flask中,如果要在运行中读取配置文件中的参数,需要使用Flask核心对象app来查找,但是之前讲过,如果反复导入app,或造成循环导入,那么解决方法就是使用current_app,current_app其实就是指代的app, 例如,读取配置文件中的PER_PAGE变量: 因为之前我们做了这个操作: def create_app(): app = Flask(__name__) app.config.from_object("app.setting") app.config.from_object("app.secure") register_blueprint(app) return app 把setting.py和secure.py中的配置加到了Flask内置config对象中,所以要这样读取配置文件中的变量: current_app.config['PER_PAGE'] 欲知后事如何,请看下回分解,记得点个赞~感谢

优秀的个人博客,低调大师

SQL Serever学习9——基础查询语句

SQL语言概述 SQL是结构化查询语言(Structure Query Language),1974年提出,1979年被IBM实现,SQL语言已经成为关系型数据库的标准语言。 包括: DDL数据定义语言 语句有CREATE ,ALTER ,DROP,操作表,视图,触发器,存储过程 DML数据操作语言 语句有SELECT ,INSERT , UPDATE , DELETE,用于检索和操作数据 DCL数据控制语言 语句有GRANT , DENY , REVOKE,只有sysadmin,数据库创建者,拥有者,安全管理员有权利执行,用来设置或更改数据库用户或角色权限 流程控制 常用语句有BEGIN...END , IF...ELSE , WHILE , BREAK , GOTO , WAITFOR , RETURN等语 逻辑运算符 AND OR NOT ALL,所有表达式为true才为true ANY,表达式中一个为true则为true BETWEEN ,在某个范围内则为true EXISTS IN ,操作数为表达式列表中的一个则为true。 语句基本格式 SELECT * FROM 表名 WHERE 条件 GROUP BY 字段 HAVING 表达式 ORDER BY 字段 ASC| DESC 说明:GROUP BY子句后可以使用HAVING 短语,用来分组后筛选,HAVING 必须跟随ORDER BY子句使用。 默认情况,查询结构表的标题可以是表的字段名,也可以无标题,还可以使用AS 对字段标题进行修改 USE 销售管理 GO SELECT 商品名称,型号,销售价-进价 AS 差价,库存 FROM 商品表 GO 使用查询生成新表,或者临时表 USE 销售管理 GO SELECT 商品名称,型号,销售价-进价 AS 差价,库存 INTO 商品表附表 FROM 商品表 GO 结果 临时表的使用 临时表在本次服务器连接过程中有效,一旦服务器断开连接,临时表失效,并被删除。 USE 销售管理 GO SELECT 商品名称,型号,销售价-进价 AS 差价,库存 INTO #临时商品表 FROM 商品表 GO 快速生成数据表结构(空白哦) 因为WHERE 1=2不成立,所以就不会检索出符合条件的数据,生成的是一个没有数据的空白表 USE 销售管理 GO SELECT 商品名称,型号,销售价-进价 AS 差价,库存 INTO #商品表副本 FROM 商品表 WHERE 1=2 GO SELECT * FROM #商品表副本 SQL汇总查询 聚合函数 常用的聚合函数有6个: COUNT(*),统计所有记录个数 COUNT[ DISTINCT] 字段,统计字段中值的个数 SUM 字段,对指定字段(数值型)求和 AVG 字段,对指定字段(数值型)求平均值 MAX 字段,求一个字段最大值 MIN 字段,求一个字段最小值 分组查询语句 有时候统计每种商品销售总金额,需要对销售表中销售金额进行汇总,然后再进行操作,这就是分组查询。 GROUP BY子句实现, GROUP BY 字段 HAVING 分组后的筛选条件表达式 注意:BY 字段 按指定字段进行分组,字段值相同的记录放在一组,每一组汇总只有一条数据。 HAVING 的筛选是对经过分组后结果进行筛选,而不是对原始表筛选。 SELECT 子句后的字段列表,必须是聚合函数 ,或者是GROUP BY 子句中的字段。 demo.sql USE 销售管理 GO SELECT 品牌,COUNT(品牌) AS 数量 FROM 商品表 GROUP BY 品牌 --HAVING 品牌='A牌' 结果 注意这里的HAVING子句和WHERE的区别: HAVING可以有聚集函数,而WHERE子句不可以 HAVING作用于分组后的结果集,WHERE 子句作用于基本表 下面来一个小demo,用来查找某个属性的值出现最多的那个记录 原表 现在查找哪个品牌数量最多,并找出这个品牌的记录 demo USE 销售管理 GO --申明变量用来存储数量最多的品牌 DECLARE @ELE VARCHAR(20) SELECT @ELE=A.品牌 FROM (SELECT TOP 1 品牌,COUNT(品牌) AS 数量 FROM 商品表 GROUP BY 品牌 ORDER BY 数量 DESC) A --print @ele SELECT * FROM 商品表 WHERE 品牌=@ELE 结果集 汇总合计函数ROLLUP(在sqlserver2008叫做COMPUTE) 使用这个函数,需要最分组函数的最后添加with rollup,然后会在最后多一行。 分组 USE 销售管理 GO SELECT 品牌,COUNT(品牌) AS 数量 FROM 商品表 GROUP BY 品牌 使用ROLLUP汇总 USE 销售管理 GO SELECT 品牌,COUNT(品牌) AS 数量 FROM 商品表 GROUP BY 品牌 WITH ROLLUP 连接查询 就是多个表单的关联查询 INNER JOIN,内连接 LEFT JOIN,左连接,结果包含满足条件的行和左侧表的全部行,使用NULL值代替无法匹配的值 RIGHT JOIN,右连接 FULL JOIN,全连接,结果包含满足条件的行和2侧表的全部行 CROSS JOIN,交叉连接,结果包含2个表的所有行的组合,2个表的笛卡尔操作,用的不多 内连接范例 使用sqlserver语法 USE 销售管理 GO SELECT A.商品名称,A.品牌,A.销售价,B.类型名称 FROM 商品表 A,商品类型表 B WHERE A.类型=B.类型编号 使用ANSI语法 USE 销售管理 GO SELECT A.商品名称,A.品牌,A.销售价,B.类型名称 FROM 商品表 A INNER JOIN 商品类型表 B ON A.类型=B.类型编号 注意:<表名> A的意思是将某个表在这一次查询红命名为A,这样在整个查询中都可以使用A代替该表,简化操作。 子查询 子查询出现的形式: 多数情况出现在WHERE 子语句中 出现在外部查询的SELECT 子语句中 出现在外部查询的FROM 子句中,即把查询结果集看做另外一张表 使用比较运算符的子查询 /*查询一级买家信息*/ SELECT * FROM 买家表 WHERE 级别= (SELECT 级别编号 FROM 买家级别表 WHERE 级别名称='一级') 使用ALL ANY运算符的子查询 当子查询返回的是单列多值,使用ALL ANY和比较运算符构成特殊查询 >ANY,表示大于子查询结果的某个值,就是大于查询结果最小值 =ANY,等于查询结果的某个值,相当于IN <ANY,小于查询结果的最大值 >ALL,大于查询结果最大值 !=ALL,相当于NOT IN 比如查询那些台式电脑比笔记本电脑的进价还要贵 /*查询那些台式电脑比笔记本电脑的进价还要贵*/ SELECT * FROM 商品表 WHERE 商品名称='台式机' AND 进价>ANY (SELECT 进价 FROM 商品表 WHERE 商品名称='笔记本') 使用IN运算符的子查询 比如查询进价大于5000的商品销售情况 /*查询进价大于5000的商品销售情况*/ SELECT 商品编号,买家编号 FROM 销售表 WHERE 商品编号 IN (SELECT 商品编号 FROM 商品表 WHERE 进价>5000) 使用EXISTS运算符的子查询 用来判断子查询是否有结果返回,NOT EXISTS的作用刚好相反 比如查询至少有一次实际销售价比进价还低的商品信息 /*查询至少有一次实际销售价比进价还低的商品信息*/ SELECT * FROM 商品表 A WHERE EXISTS (SELECT * FROM 销售表 B WHERE A.商品编号=B.商品编号 AND B.实际销售价格<A.进价) 由于不需要子查询返回具体值,所以这种子查询的通常返回的列为*的格式 有个查询很难理解,记录如下 查询销售表每种商品(由商品编号区分)销售价格最贵的销售情况 分析:首先将商品种类分组 SELECT 商品编号,MAX(实际销售价格) FROM 销售表 GROUP BY 商品编号 这里还不能输出要求的信息,所以还要使用自连接(自己与自己的一个副本连接) 原表 经过筛选 /*查询销售表每种商品(由商品编号区分)销售价格最贵的销售情况*/ SELECT * FROM 商品表 A WHERE 销售价= (SELECT MAX(销售价) FROM 商品表 B WHERE A.品牌=B.品牌) ORDER BY 商品编号 数据库中数据的管理 插入数据INSERT 使用INSERT语句插入数据进数据表,有2种方式:插入单行数据(使用VALUES),插入多行数据(使用SELECT) 插入单行数据 /*插入单行数据*/ INSERT INTO 买家表(买家编号,买家名称,买家电话,级别) VALUES('M05','薛松','5362313','J02'); 当插入数据的数量和顺序和表中字段一一对应,可以省略字段名列表 /*插入单行数据*/ INSERT INTO 买家表 VALUES('M06','宋松','5362220','J02'); 插入多行数据 新建一张表,名为“高价销售表类”,结构与销售表相同,将销售表的实际销售价格>3000的记录插入该表。 /*插入多行数据*/ --建立一张空表 SELECT * INTO 高价销售表 FROM 销售表 WHERE 1=2 GO --插入多行数据 INSERT INTO 高价销售表(商品编号,买家编号,实际销售价格,销售日期,销售数量) SELECT 商品编号,买家编号,实际销售价格,销售日期,销售数量 FROM 销售表 WHERE 实际销售价格>3000 GO 为了建立一张空白表,查询条件WHERE 1=2永远不成立,这个是一个常用的方法。 将所有一级买家的信息存入新表“高级买家” /*插入多行数据*/ --创建新表 SELECT * INTO 高级买家 FROM 买家表 WHERE 1=2 GO --添加数据 INSERT INTO 高级买家 SELECT 买家表.* FROM 买家表,买家级别表 WHERE 买家表.级别=买家级别表.级别编号 AND 级别名称='一级' GO 由于查询过程使用了2个表了,所以SELECT 语句要声明,只要买家表的列 修改数据UPDATE 普通修改 因为与A品牌的合作有了新政策,所有A品牌商品进货价下调5% /*普通修改数据*/ UPDATE 商品表 SET 销售价=销售价*0.95 WHERE 品牌='A牌' 带子查询的修改 为了增加耗材商品的销售份额,公司决定将所有耗材商品销售价格下调5% /*子查询修改数据*/ UPDATE 商品表 SET 销售价=销售价*0.95 WHERE 商品表.类型= (SELECT 类型编号 FROM 商品类型表 WHERE 类型名称='耗材') 删除数据DELETE 删除普通数据 删除销售表4所有B牌的商品购买信息 /*删除数据*/ SELECT * INTO 销售表4 FROM 销售表 GO DELETE FROM 销售表4 WHERE 品牌='B牌' 删除子查询 删除销售表4中所有买家名称为“个人”的买家购买信息 /*删除数据*/ SELECT * INTO 销售表4 FROM 销售表 GO DELETE FROM 销售表4 WHERE 买家编号= (SELECT 买家编号 FROM 买家表 WHERE 买家名称='个人') 清空数据表 /*删除数据*/ SELECT * INTO 销售表4 FROM 销售表 GO TRUNCATE TABLE 销售表4 注意:TRUNCATE TABLE 和不带条件的DELETE最终效果都是清空表中所有数据,但是在执行上TRUNCATE TABLE 更高,速度更快,因为他不记录事务日志,会释放数据,索引占据的空间,删除的数据不可恢复。

优秀的个人博客,低调大师

(三)Java并发学习笔记--线程封闭

线程封闭 实现好的并发是一件困难的事情,所以很多时候我们都想躲避并发。避免并发最简单的方法就是线程封闭。什么是线程封闭呢? 就是把对象封装到一个线程里,只有这一个线程能看到此对象。那么这个对象就算不是线程安全的也不会出现任何安全问题。实现线程封闭有哪些方法呢? 1. ad-hoc线程封闭 这是完全靠实现者控制的线程封闭,他的线程封闭完全靠实现者实现。也是最糟糕的一种线程封闭。所以我们直接把他忽略掉吧。 2. 栈封闭 栈封闭是我们编程当中遇到的最多的线程封闭。什么是栈封闭呢?简单的说就是局部变量。多个线程访问一个方法,此方法中的局部变量都会被拷贝一分儿到线程栈中。所以局部变量是不被多个线程所共享的,也就不会出现并发问题。所以能用局部变量就别用全局的变量,全局变量容易引起并发问题。 3. ThreadLocal封闭 使用ThreadLocal是实现线程封闭的最好方法,有兴趣的朋友可以研究一下ThreadLocal的源码,其实ThreadLocal内部维护了一个Map,Map的key是每个线程的名称,而Map的值就是我们要封闭的对象。每个线程中的对象都对应着Map中一个值,也就是ThreadLocal利用Map实现了对象的线程封闭。这里就不说ThreadLocal的使用方法了,度娘一下便知。 总之,当我们要用线程封闭来避免并发问题的时候,最好使用的就是 【栈封闭】 和 【ThreadLocal】。

优秀的个人博客,低调大师

PHP学习10——Cookie和Session技术

主要内容: Cookie技术 创建cookie 查看cookie 读取cookie 用cookie记录访问时间和次数 删除cookie cookie的生命周期 Session技术 session工作原理 session控制 session的存储结构 传递session_id cookie和session是2中不同的存储机制。 cookie是从一个web页面到另一个web页面的数据传递方法,它被存储在客户端。 session是让数据在页面中持续有效的方法,它存储在服务器。 Cookie技术 cookie将浏览器网页的特定信息以文件形式保存在客户端硬盘中,再次访问该网站,浏览器首先尝试读取cookie文件,读取到数据供浏览器使用。 常见的应用是登陆空间、博客,读取每个人的个性化设置。 cookie常用语3个方面: 记录访客的某些信息 在页面之间传递信息(通常cookie不会用来保存密码,但是在一个页面设置的数据要在另一个页面使用,可以通过cookie技术实现) 将查看的web网页存储在cookie临时文件中,可以加快网页的访问速度。 创建cookie /*name是cookie变量名,value是值,time是失效时间(默认为0), path是cookie在服务器端的有效路径,domain是cookie有效域名, secure是否仅通过安全的https协议传输(默认为0),httponly是否仅通过http协议传输(默认为0) */ //setcookie($name,$value,$time,$path,$domain,$secure,$httponly); cookie.php <?php //创建cookie $sta=setcookie('testcookiename','testvalue',time()+60*60); if($sta){ echo 'Cookie设置成功!'; } ?> 输出 Cookie设置成功! chrome浏览器查看cookie 打开浏览器的设置》高级设置》 隐私设置/内容设置》所有cookie和网站数据 》找到你的网站名,以及cookie的名字,点击打开即可看到保存到cookie的内容。 读取cookie PHP中使用超全局变量$_COOKIE来读取浏览器端的Cookie值 如果没有设置cookie的失效时间,cookie会在浏览器关闭后失效。 readcookie.php <?php //创建cookie $sta=setcookie('testcookiename','testvalue',time()+60*60); if($sta){ echo 'Cookie设置成功!<br/>'; } //创建cookie setcookie('name','Tom'); setcookie('age','25'); print_r($_COOKIE); ?> 输出 Cookie设置成功! Array ( [testcookiename] => testvalue [name] => Tom [age] => 25 ) 使用cookie记录访客访问时间和访问次数 demo.php <?php //访问时间 echo "访问时间<br/>"; if(!isset($_COOKIE['visittime'])){ setcookie('visittime',date('Y-m-d H:i:s')); echo '这是您第一次方位本网站<br/>'; }else{ echo '您上一次访问本网站是在:'.$_COOKIE['visittime']."<br/>"; echo '本次访问时间是:'.date('Y-m-d H:i:s')."<br/>"; } //访问次数 echo "访问次数<br/>"; if(!isset($_COOKIE['num'])){ setcookie('num',1); echo '欢迎您首次访问本网站<br/>'; }else{ setcookie('num',$_COOKIE['num']+1); echo '这是您今天第'.$_COOKIE['num']." 次访问本网站<br/>"; } ?> 第1次输出 第8次输出 第13次输出 删除cookie 没有设置失效时间,cookie随浏览器关闭而失效,如果要在浏览器关闭之前删除cookie,有2种方法。 1、浏览器手动删除cookie 2、通过程序删除cookie 使用setcookie()删除cookie,将要删除的cookie有效时间设置为当前时间之前即可, setcookie.php <?php //设置cookie setcookie('name','Tom'); print_r($_COOKIE); ?> 输出 Array ( [name] => Tom ) removecookie.php <?php //删除cookie setcookie('name','Tom',time()-1); print_r($_COOKIE); ?> 输出 Array ( ) Cookie的生命周期 如果不设置cookie过期时间,则表示他的生命周期会随着浏览器关闭而结束,这种cookie叫做会话cookie,一般不会保存在硬盘中,而是保存在内存中。 如果设置了cookie的过期时间则浏览器会把cookie保存在硬盘中,再次打开浏览器依然会生效,知道他的有效时间超时。 但是有的情况是不遵循这个说法的,现在的浏览器通常会限制每个域名的cookie数量和大小,因此在超过这个限制后,cookie即使没有超过时间也可能会被删除。 Session技术 与cookie相比,,session文件保存的数据在PHP中以变量形式创建,创建的session变量在生命周期(20分钟)中可以被跨页面的请求所使用。另外session文件是存储在服务器端的,比cookie存储在客户端安全,而且也没有类似cookie的存储长度限制。 在计算机术语中,session指一个终端用户与交互系统进行通信的时间间隔,通常指从注册进入系统到注销推相互系统所经过的时间,因此可以说session是一个特定的时间概念。 session工作原理 启动一个session会话时,程序会随机生成一个唯一的session_id,他也是session的文件名,此时session_id存储在服务器,当关闭页面后,session_id会自动注销,重新登录页面会再次生成一个随机且唯一的session_id。 session的功能 由于网页是一种无状态的连接程序,因此无法得知用于的浏览状态。在网上购物时,把很多商品加入了购物车,而在结账时网页却不知道你购物车有哪些物品,通过session可以记录这些信息,供用户再次使用。session适用于存储信息量比较少的情况。 session控制 4个步骤: 启动会话 注册会话 使用会话 删除会话 启动session session_start();注意启动一个新的会话之前不可以有其他输出,否则出现错误,这类似于创建cookie。 注册session 所有session变量都会保存在数组$_SESSION中,只需给数组添加元素即可。$_SESSION['name']='Jim'; 在php.ini中有session的保存路径 session.php <?php //开启session session_start(); //创建2个session变量 $_SESSION['username']='Jim'; $_SESSION['password']='123456'; echo "Session创建成功<br/>"; ?> session变量的存储结构 变量名|类型:长度:变量值; 使用session 使用方法类似cookie,直接读取数组 删除session unset($_SESSION['username']),删除单个session $_SESSION=array(),注销所有session session_destroy(),先注销session,然后结束当前session,释放session中所有资源,彻底将session销毁 unsetsession.php <?php //开启session session_start(); //创建2个session变量 $_SESSION['username']='Jim'; $_SESSION['password']='123456'; //使用session if(!empty($_SESSION['username'])){ $username=$_SESSION['username']; echo 'session变量值赋值成功<br/>'; }else{ echo '该session变量未注册<br/>'; } //删除session unset($_SESSION['username']); if(empty($_SESSION['username'])){ echo 'session注销成功<br/>'; } ?> 输出 destroy_session.php <?php //开启session session_start(); //创建2个session变量 $_SESSION['username']='Jim'; $_SESSION['password']='123456'; //使用session if(!empty($_SESSION['username'])){ $username=$_SESSION['username']; echo 'session变量值赋值成功<br/>'; }else{ echo '该session变量未注册<br/>'; } //删除session unset($_SESSION['username']); if(empty($_SESSION['username'])){ echo 'session注销成功<br/>'; } //清空session $_SESSION=array(); if(empty($_SESSION['username'])&&empty($_SESSION['password'])){ echo '全部session注销成功<br/>'; } //销毁session session_destroy(); echo 'session已经彻底销毁<br/>'; ?> 输出 session变量值赋值成功 session注销成功 全部session注销成功 session已经彻底销毁 传递session_id 基于cookie的方式传递session。这种方法有限制,客户端可以禁用cookie 通过URL参数传递,直接将session_id放入URL中 session_cookie.php <?php //开启session session_start(); //设置cookie setcookie(session_name(),session_id(),time()+60*60*24); //创建2个session变量 $_SESSION['username']='Jim'; $_SESSION['password']='123456'; echo 'session保存成功<br/>'; ?> showsession.php <?php //开启session session_start(); //设置cookie //setcookie(session_name(),session_id(),time()+60*60*24); //创建2个session变量 //$_SESSION['username']='Jim'; //$_SESSION['password']='123456'; echo 'session保存成功<br/>'.$_SESSION['username'].$_SESSION['password']='123456'; ?> 输出 session保存成功 Jim123456 通过URL传递session session_url.php <?php //开启session session_start(); //创建2个session变量 $_SESSION['username']='Jim'; $_SESSION['password']='123456'; //输出下一个页面链接 echo '<a href=test.php?'.session_name().'='.session_id().'>test.php</a>'; ?> test.php <?php session_start(); echo '用户名:'.$_SESSION['username'].'<br/>'; echo '密码:'.$_SESSION['password'].'<br/>'; ?> 输出 在PHP中有一个预定义常量SID,格式为name=ID,包含了session名和sessionid 所有可以这样使用 //输出下一个页面链接 echo '<a href=test.php?'.SID.'>test.php</a>'; web页面非常重要的cookie技术和session技术,这里核心要明白session和cookie的工作原理和不同之处。 ok,就到这里。

优秀的个人博客,低调大师

JVM虚拟机学习总结(一)

1.JVM类加载器原理JVM的工作流程是.java文件通过javac编译成.class文件,然后利用类加载器将字节码文件加载到内存中,然后通过解释器来编译成机器代码,然后执行。 每一个类加载器都有同一个最终的父类BootStrapClassLoader,在类加载器检查类是否已存在在加载器的类库中时,类加载器的执行顺序为: CustomClassLoader(用户自定义的类加载器) ↓ AppClassLoader(系统类加载器) ↓ EcxtensionClassLoader(扩展类加载器) ↓ BootStrapClassLoader(最顶层加载器) 在加载器执行的过程中如果CustomClassLoader加载器在自己的库中找不到对应的类文件,就会委托给AppClassLoader继续查找,同理如果AppClassLoader在自己的库中也找不到对应的类文件就会继续委托它的上一层EcxtensionClassLoader类加载器去查找,以此类推最终如果BootStrapClassLoader加载器也找不到对应的类文件,就会尝试去加载这个类。 尝试加载类的顺序为: LoadJRElibrt.jar或者-xbootClassPath指定的jar包(由BootStrapClassLoader加载器加载) ↓ LoadJRElibertr*.jar或者-Djava.ert.dirs指定目录下的jar包(由EcxtensionClassLoader加载器加载) ↓ loadCLASSPATH或者-Djava.class.path所指定目录下的jar包(由AppClassLoader加载器加载) ↓ 通过java.lang.ClassLoader的自定义加载器加载class(由CustomClassLoader加载器加载)。 在JVM中累的完整标识:ClassLoader+package+className;类加载器的可见性限制:下层的加载器能够看到上层加载器中的类,反之则不被允许,也就是说委托只能从下到上;不可卸载类:类加载器可以加载一个类,但是它不能卸载一个类,但是类加载器可以被删除以及被创建;JVM的接下来的执行流程为: loading(加载类) ↓ verifying(验证java以及JVM规范) ↓ preparing(为类分配内存,确定类的属性、方法以及数据结构) ↓ resolving(将该类常量池中的符号引用改为直接引用) ↓ initialing(初始化类的局部变量,为静态域赋值,同时执行静态初始化块) 2.JVM的运行数据区域运行数据区域主要包含六个模块,六个模块又分别属于每个线程独立拥有的以及所有线程共有的模块 每个线程独立拥有的如下: java栈程序代数寄存器(PC寄存器)本地方法栈所有线程共有的如下: java堆方法区域运行常量池Java栈:java栈是每个线程单独拥有的,线程启动时创建,java栈中存放着一系列的栈帧,JVM只能进行压入(push)和弹出(Pop)栈帧这两种操作,每调用一个方法JVM就向Java栈中压入一个栈帧,方法结束返回时弹出栈帧,如果方法执行时出现异常可以调用printStackTrace方法查看栈的情况。PC计数器:每个线程都有一个PC计数器,当线程启动时PC计数器被创建,这个计数器存放当前正在被执行的字节码指令。本地方法栈:程序通过JNI(注释1)调用本地代码,就根据本地方法语言类型建立相同的栈。Java堆:Java堆中存放的是程序创建的对象或者实例,这个区域对JVM的性能影响很大,垃圾回收机制处理正是指的这一块区域。方法区域:方法区域是JVM中所有线程共享的,当启动一个JVM实例时方法区域被创建用来存放常量池、有关域和方法的信息,静态变量、类和方法的字节码。运行常量池:存放类和接口的常量除此之外还存放方法和域所有的引用,当一个方法或域被引用时JVM就通过运行常量池中的这些引用来查找它们的实际地址。 注释1:JNI是Java Native Interface的缩写,它提供了若干的API实现了Java和其他语言的通信(主要是C&C++)。从Java1.1开始,JNI标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他编程语言,只要调用约定受支持就可以了。使用java与本地已编译的代码交互,通常会丧失平台可移植性。但是,有些情况下这样做是可以接受的,甚至是必须的。例如,使用一些旧的库,与硬件、操作系统进行交互,或者为了提高程序的性能。JNI标准至少要保证本地代码能工作在任何Java虚拟机环境。 借鉴:http://blog.csdn.net/bingduanlbd/article/details/8363734

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

用户登录
用户注册