首页 文章 精选 留言 我的

精选列表

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

【Android进阶学习】实现没有标题栏的窗口和全屏显示

在Android实现没有标题栏的方法有两种: 在代码中添加 requestWindowFeature(Window.FEATURE_NO_TITLE); 在清单文件AndroidManifest.xml中添加 android:theme="@android:style/Theme.NoTitleBar" 具体的代码如下: 第一种: MainActivity.java packagecom.lingdududu.test; importandroid.app.Activity; importandroid.os.Bundle; importandroid.view.Window; publicclassMainActivityextendsActivity{ /**Calledwhentheactivityisfirstcreated.*/ privatebooleancatchHomeKey=false; publicvoidonCreate(BundlesavedInstanceState){ super.onCreate(savedInstanceState); this.requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.main); } } 第二种: AndroidManifest.xml <?xmlversion="1.0"encoding="utf-8"?> <manifestxmlns:android="http://schemas.android.com/apk/res/android" package="com.lingdududu.test" android:versionCode="1" android:versionName="1.0"> <uses-sdkandroid:minSdkVersion="10"/> <applicationandroid:icon="@drawable/icon"android:label="@string/app_name"> <activityandroid:name=".MainActivity" android:label="@string/app_name" android:theme="@android:style/Theme.NoTitleBar"> <intent-filter> <actionandroid:name="android.intent.action.MAIN"/> <categoryandroid:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> </application> </manifest> 效果图: 如果想让窗口全屏显示: 将下面两段代码分别替换上面的两段设置无标题的代码就可以了 getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); android:theme="@android:style/Theme.NoTitleBar.Fullscreen" 效果图: 本文转自 lingdududu 51CTO博客,原文链接: http://blog.51cto.com/liangruijun/732128

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

《从零开始学Swift》学习笔记(Day 35)——会使用下标吗?

看下面的示例代码是不是使用过: 1 <span style= "font-size:14px;" >var studentList:String[] = [ "张三" , "李四" , "王五" ]<br>studentList[ 0 ] = "诸葛亮" <br><br>var studentDictionary =[ 102 : "张三" , 105 : "李四" , 109 : "王五" ]<br>studentDictionary[ 110 ] = "董六" </span> 在访问数组和字典的时候,可以采用下标访问。其中数组的下标是整数类型索引,字典的下标是它的“键”。 下标 Swift中的下标相当于Java中的索引属性和C#中的索引器。 下标访问的语法格式如下: 1 <span style= "font-size:14px;" >面向对象类型类型名 { <br> 其他属性<br> ...<br> subscript(参数: 参数数据类型) -> 返回值数据类型 { <br> get{ <br> return 返回值<br> } <br><br> set(新属性值) {<br> ...<br> } <br> } <br>}</span> 下标也有类似于计算属性的getter和setter访问器。 getter访问器是一个方法,在最后使用return语句将计算结果返回。 setter访问器“新属性值”是要赋值给属性值。参数的声明可以省略,系统会分配一个默认的参数newValue。 示例:二维数组 在Swift中没有提供二维数组,只有一维数组Array。可以自定义一个二维数组类型,然后通过两个下标参数访问它的元素,形式上类似于C语言的二维数组。 采用下标的二维数组示例代码如下: 1 <span style= "font-size:14px;" >structDoubleDimensionalArray { //定义了二维数组结构体<br> <br> let rows: Int, columns: Int //存储属性rows和columns<br> var grid: [Int]<br> <br> init(rows: Int, columns: Int) { //构造函数<br> self.rows = rows<br> self.columns = columns<br> grid = Array(count: rows * columns,repeatedValue: 0) //初始化存储属性grid<br> }<br> <br> subscript(row: Int, col: Int) -> Int { //定义下标<br> <br> get {<br> return grid[(row * columns) + col] <br> }<br> <br> set (newValue1){<br> grid[(row * columns) + col] =newValue1 <br> }<br> }<br> <br>}<br><br>var ary2 =DoubleDimensionalArray(rows: 10, columns: 10)//创建并初始化10×10大小的二维数组<br><br>//初始化二维数组<br>for var i = 0; i < 10;i++ {<br> for var j = 0; j < 10; j++ {<br> ary2[i,j] = i * j <br><br> }<br>}<br><br>//打印输出二维数组<br>for var i = 0; i < 10;i++ {<br> for var j = 0; j < 10; j++ {<br> print("\t \(ary2[i,j])")<br> }<br> print("\n")<br>}<br></span> 输出结果如下: 0 000000000 0123456789 024681012141618 0369121518212427 04812162024283236 051015202530354045 061218243036424854 071421283542495663 081624324048566472 091827364554637281 本文转自 tony关东升 51CTO博客,原文链接:http://blog.51cto.com/tonyguan/1746615,如需转载请自行联系原作者

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

《从零开始学Swift》学习笔记(Day 42)——构造函数调用规则

在构造函数中可以使用构造函数代理帮助完成部分构造工作。类构造函数代理分为横向代理和向上代理,横向代理只能在发生在同一类内部,这种构造函数称为便利构造函数。向上代理发生在继承的情况下,在子类构造过程中,要先调用父类构造函数初始化父类的存储属性,这种构造函数称为指定构造函数。 构造函数调用规则 Person和Student类示例: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class Person{ varname:String varage:Int funcdescription()->String{ return "\(name)年龄是:\(age)" } convenienceinit(){ //便利构造函数 self.init(name: "Tony" ) self.age= 18 } convenienceinit(name:String){ //便利构造函数 self.init(name:name,age: 18 ) } init(name:String,age:Int){ //指定构造函数 self.name=name self.age=age } } class Student:Person{ varschool:String init(name:String,age:Int,school:String){ //指定构造函数 self.school=school super .init(name:name,age:age) } convenienceoverrideinit(name:String,age:Int){ //便利构造函数 self.init(name:name,age:age,school: "清华大学" ) } } letstudent=Student() print( "学生:\(student.description())" ) 构造函数之间的调用形成了构造函数链,如图所示。 Swift限制构造函数之间的代理调用的规则有3条,如下所示。 指定构造函数必须调用其直接父类的的指定构造函数。从图可见,Student中的④号指定构造函数调用Person中的③号指定构造函数。 便利构造函数必须调用同一类中定义的其他构造函数。从图可见,Student中的⑤号便利构造函数调用同一类中的④号便利构造函数,Person中的①号便利构造函数调用同一类中的②号便利构造函数。 便利构造函数必须最终以调用一个指定构造函数结束。从图可见,Student中的⑤号便利构造函数调用同一类中的④号指定构造函数,Person中的②号便利构造函数调用同一类中的③号指定构造函数。 本文转自 tony关东升 51CTO博客,原文链接:http://blog.51cto.com/tonyguan/1747498,如需转载请自行联系原作者

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

《从零开始学Swift》学习笔记(Day 33)——属性观察者

为了监听属性的变化,Swift提供了属性观察者。属性观察者能够监听存储属性的变化,即便变化前后的值相同,它们也能监听到。 属性观察者主要有以下两个: willSet:观察者在修改之前调用。 didSet:观察者在修改之后立刻调用。 属性观察者的语法格式如下: 面向对象类型类型名{ 1 2 3 4 5 6 7 8 9 10 ... var存储属性:属性数据类型=初始化值{ willSet(新值){ //定义willSet观察者。“新值”是传递给willSet观察者的参数,它保存了将要替换原来属性的新值 ... } didSet(旧值){ //定义didSet观察者。“旧值”是传递给didSet观察者的参数,它保存了被新属性替换的旧值。 ... } } } 属性观察者的语法格式比计算属性要混乱。 属性观察者可以在类和结构体中使用,不能在枚举中使用。 示例代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class Employee{ varno:Int= 0 varname:String= "Tony" { willSet(newNameValue){ //定义name属性的willSet观察者,newNameValue是由我们分配的传递新值的参数名 print( "员工name新值:\(newNameValue)" ) } didSet(oldNameValue){ //定义name属性的didSet观察者,oldNameValue是由我们分配的传递旧值的参数名 print( "员工name旧值:\(oldNameValue)" ) } } varjob:String? varsalary:Double= 0 vardept:Department? } structDepartment{ varno:Int= 10 { willSet{ //定义no属性的willSet观察者,注意这里没有声明参数,但是我们可以在观察者内部使用newValue print( "部门编号新值:\(newValue)" ) } didSet{ //定义no属性的didSet观察者,注意这里也没有声明参数,但是我们可以在观察者内部使用oldValue print( "部门编号旧值:\(oldValue)" ) } } varname:String= "RESEARCH" } varemp=Employee() emp.no= 100 emp.name= "Smith" vardept=Department() dept.no= 30 上述代码运行结果如下: 员工name新值:Smith 员工name旧值:Tony 部门编号新值:30 部门编号旧值:10 本文转自 tony关东升 51CTO博客,原文链接:http://blog.51cto.com/tonyguan/1746600,如需转载请自行联系原作者

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

Hadoop HDFS概念学习系列之HDFS的特性和目标(九)

HDFS的特性 HDFS和传统的分布式文件系统相比较,具有以下明显的特性:高度容错,可扩展性及可配置性强。由于容错性高,因此非常适合部署利用通用的硬件平台构建容错性很高的分布式系统。容易扩展是指扩展无须改变架构只需要增加节点即可,同时可配置性很强。跨平台。使用Java语言开发,支持多个主流平台环境。shell命令接口。和Linux文件系统一样,拥有文件系统shell命令,可直接操作HDFS。Web界面。NameNode和DataNode有内置的Web服务器,方便用户检查集群的当前状态。文件权限和授权。拥有和Linux系统类似的文件权限管理。机架感知功能。在调度任务和分配存储空间时系统会考虑节点的物理位置,从而实现高效访问和计算。 安全模式。一种维护需要的管理模式。Rebalancer。当DataNode之间数据不均衡时,可以平衡集群上的数据负载,实现数据负载均衡。升级和回滚。在软件更新后有异常发生的情形下,能够回滚到HDPS升级之前的状态。 HDFS的目标 HDFS作为Hadoop的分布式文件存储系统和传统的分布式文件系统有很多相同的设计目标。例如,在可伸缩性及可用性上。但是HDFS的设计前提是假设和较早的文件系统有着明显的不同之处。下面简述HDFS的设计思路和目标: 1.硬件错误 硬件组件错误是常态,而非异常情况。HDFS可能由成百上千的服务器组成,每一个服务器都是廉价通用的普通硬件,任何一个组件都有可能一直失效,因此错误检测和快速、自动恢复是HDFS的核心架构目标,同时能够通过自身持续的状态监控快速检测冗余并回复失效的组件。 2.流式数据访问 运行在HDFS上的应用和普通的应用不同,需要流式访问它们的数据集。HDFS的设计中更多考虑到了数据批处理,而不是用户交互处理。相比数据访问的低延迟,HDFS应用要求能够高速率、大批量地处理数据,极少有程序对单一的读写操作有严格的响应时间要求,更关键的问题在于数据访问的高吞吐量。POSIX标准设置的很多硬性约束对HDFS应用系统不是必需的。为了提高数据的吞吐量,在一些关键方面对POSIX的语义做了一些修改。 3.大规模数据集 运行在HDFS土的应用具有很大的数据集。HDFS上的一个典型文件,大小一般都在GB至TB。因此,需要调节HDFS以支持大文件存储。HDFS应该能提供整体较高的数据传输带宽,能在一个集群里扩展到数百个节点。一个单一的HDFS实例应该能支撑千万计的文件。 4.简化一致性模型 HDFS应用需要一个“一次写入多次读取”的文件访问模型。一个文件经过创建、写入和关闭之后就不需要改变了。这一假设简化了数据一致性问题,并且使高吞吐量的数据访问成为可能。MapReduce应用或网络爬虫应用都非常适合这个模型。目前还有计划在将来扩充这个模型,使之支持文件的附加写操作。 5.移动计算代价比移动数据代价低 一个应用请求的计算,离它操作的数据越近就越高效,这在数据达到海量级别的时候更是如此。将计算移动到数据附近,比之将数据移动到应用所在之处显然更好,HDFS提供给应用这样的接口。 6.可移植性 HDFS在设计时就考虑到平台的可移植性,这种特性方便了HDFS作为大规模数据应用平台的推广。 本文转自大数据躺过的坑博客园博客,原文链接:http://www.cnblogs.com/zlslch/p/5081509.html,如需转载请自行联系原作者

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

Spark RDD概念学习系列之Spark的算子的作用(十四)

Spark的算子的作用 首先,关于spark算子的分类,详细见http://www.cnblogs.com/zlslch/p/5723857.html 1、Transformation 变换/转换算子 1、map算子 2、flatMap算子 3、mapPartitions算子 4、union算子 5、cartesian算子 6、grouBy算子 7、filter算子 8、sample算子 9、cache算子 10、persist算子 11、mapValues算子 12、combineByKey算子 13、reduceByKey算子 14、join算子 2、Action 行动算子 1、foreach算子 2、saveAsTextFile算子 3、collect算子 4、count算 简单地总结: 通过Action算子,触发Spark提交作业。 通过Cache算子,将数据缓存到内存。 图1 Spark算子和数据空间 上图描述了Spark的输入、 运行转换、 输出。 在运行转换中通过算子对RDD进行转换。算子是RDD中定义的函数,可以对RDD中的数据进行转换和操作。 1)输入:在Spark程序运行中,数据从外部数据空间(如分布式存储:textFile读取HDFS等,parallelize方法输入Scala集合或数据)输入Spark,数据进入Spark运行时数据空间,转化为Spark中的数据块,通过BlockManager进行管理。 2)运行:在Spark数据输入形成RDD后便可以通过变换算子,如fliter等,对数据进行作并将RDD转化为新的RDD,通过Action算子,触发Spark提交作业。 如果数据需要复用,可以通过Cache算子,将数据缓存到内存。 3)输出:程序运行结束数据会输出Spark运行时空间,存储到分布式存储中(如saveAsTextFile输出到HDFS),或Scala数据或集合中(collect输出到Scala集合,count返回Scala int型数据)。Spark的核心数据模型是RDD,但RDD是个抽象类,具体由各子类实现,如MappedRDD、 ShuffledRDD等子类。 Spark将常用的大数据操作都转化成为RDD的子类。 本文转自大数据躺过的坑博客园博客,原文链接:http://www.cnblogs.com/zlslch/p/5723979.html,如需转载请自行联系原作者

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

Hadoop概念学习系列之大数据、Hadoop和云计算(十三)

我们知道,Hadoop最擅长的事情就是可以高效地处理海量规模的数据,这样Hadoop就和大数据及云计算结下了不解之缘。讲解Hadoop、大数据以及云计算之间的关系,使你从大数据和云计算的角度来认识Hadoop。 大数据一般是指这样的数据:数据量巨大,需要运用新处理模式才能具有更强的决策力、洞察力和流程优化能力的海量、高增长率和多样化的信息资产。大数据可分成大数据技术、大数据工程、大数据科学和大数据应用等领域。目前人们谈论最多的是大数据技术和大数据应用,大数据工程和大数据科学尚未被重视。大数据工程指大数据的规划建设及其运营管理的系统工程;大数据科学关注的是大数据网络发展和运营过程中发现和验证大数据的规律及其与自然和社会活动之间的关系。 大数据的特征有四个层面: 第一、数据量巨大。从TB级别,跃升到PB级别; 第二、数据类型繁多。包括网络fl志、视频、图片、地理位置信息等; 第三,价值密度低。商业价值高,以视频为例.在连续不间断的监控过程中,可能有用的数据仅仅只有一两秒; 第四、处理速度快。最后这一点也和传统的数据挖掘技术有着本质的不同。业界将其归纳为4V —— Volume、Variety、Value 和Velocity。 上面我们介绍了大数据的基本概念以及其显著的特征,下面将从不同的维度来阐述大数据的核心问题。 1.数据态的多样性问题 大数据具有多态性,主要体现在数据源、结构及相关度上。在数据来源上包括(图像、视频、音频、文本、网页、数据流等;在结构上不仅仅包括结构化的数据,还包括非结构化的数据;在相关度上不仅有数据记录彼此间相关性问题,还有时间序列数据的相关性问题。 2.维度复杂性问题 首先,大数据中存在着多元空间的维度问题,例如典型的三元空间中大数据的产生、状态感应以及采集问题,这个问题在物联网中非常常见;其次,就是柔性粒度数据的传输、移动、存储及计算问题;最后,就是数据空间范围和数据密度的不均匀问题。 3.大数据存储问题 大数据最为显著的特征就是数据规模非常巨大,单机系统肯定无法解决存储问题,这就需要分布式存储系统作为大数据的存储支撑服务,而分布式存储系统需要考虑的核心问题包括:高可靠性、扩一展性、伸缩性、容灾及恢复等问题。 4.大数据计算分析问题 由大数据的特征可知,大数据在数据规模上非常巨大,要在一定的时间内达到撷取、管理、处理并整理为能够帮助企业做出经营决策更有效的资讯,传统的顺序计算模式必然不能满足这样的需求,这就要求使用集群计算系统来完成计算分析任务。基于集群的计算模型目前主要包括:基于消息传递的MPI , MapReduce计算模型、流式计算架构Storm , S4、高性能集群计算HPCC,以及基于共享内存RDD的Spark模型。 5.大数据价值挖掘问题 由于大数据的价值密度低而商业价值大,这使得大数据的价值挖掘显得格外重要,而价值挖掘主要包括两个阶段:第一个阶段就是过滤清洗,需要在尽量不损失其价值的条件下减小数据规模,同时在不改变数据基本属性的情况下采取数据清洗、抽样、去重、过滤、筛选、压缩、索引、提取元数据等方法,以直接将大数据变小;第二个阶段就是对商业价值的挖掘,主要是发挥大数据探索式考察与可视化作用,人机的交互分析可以将人的智慧融入数据,再者是通过群体智慧、社会计算、认知计算对数据价值进行提炼,从而挖掘出大数据中隐藏的商业价值。 大数据、Hadoop和云计算的关系 上面的内容讲述了大数据的基本概念及与大数据相关的几个核心问题,通过这些问题我们已对大数据有了一个初步的了解,那么大数据、Hadoop及云计算之间到底是什么关系呢?为了从大数据和云计算的角度去了解Hadoop,下面将阐述这三个概念之间的关系。 可以这样说,正是由于大数据对系统提出了很多极限的要求,不论是存储、传输还是计算,现有计算技术难以满足大数据的需求,因此整个IT架构的革命性重构势在必行,存储能力的增长远远赶不大数据的增长,设i十最合理的分层存储架构已成为信息系统的关键。分布式存储架构不仅需要scale up式的可扩展性,一也需要scale out式的可扩展性,因此大数据处理离不开云计算技术,云计算可为大数据提供弹性可扩展的基础设施支撑环境以及数据服务的高效模式,大数据则为云计算提供了新的商业价值,大数据技术与云计算技术必将有更完美的结合。 我们知道云计算的关键技术包括分布式并行计算、分布式存储以及分布式数据管理技术,而Hadoop就是一个实现了Google云计算系统的开源平台,包括并行计算模型MapReduce、分布式文件系统HDFS,以及分布式数据库Hbase,同时Hadoop的相关项目也很丰富,包括ZooKeeper , Pig , Chukwa , Hive , Elbase , Mahout等,这些项日都使得Hadoop成为一个很大很完备的生态链系统。目前使用Hadoop技术实现的云计算平台包括IBM的蓝云.雅虎、英特尔的“云计划”,百度的云计算基础架构,阿里巴巴云计算平台,以及中国移动的B igCloud大云平台。 总而言之,用一句话概括就是云计算因大数据问题而生,大数据驱动了云讨一算的发展,而Hadoop在大数据和云计算之间建起了一座坚实可靠的桥梁。 本文转自大数据躺过的坑博客园博客,原文链接:http://www.cnblogs.com/zlslch/p/5080573.html,如需转载请自行联系原作者

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

学习使用Docker、Docker-Compose和Rancher搭建部署Pipeline(一)

这篇文章是一系列文章的第一篇,在这一系列文章中,我们想要分享我们如何使用Docker、Docker-Compose和Rancher完成容器部署工作流的故事。我们想带你从头开始走过pipeline的革命历程,重点指出我们这一路上遇到的痛点和做出的决定,而不只是单纯的回顾。幸好有很多优秀的资源可以帮助你使用Docker设置持续集成和部署工作流。这篇文章并不属于这些资源之一。一个简单的部署工作流相对比较容易设置。但是我们的经验表明,构建一个部署系统的复杂性主要在于原本容易的部分需要在拥有很多依赖的遗留环境中完成,以及当你的开发团队和运营组织发生变化以支持新的过程的时候。希望我们在解决构建我们的pipeline的困难时积累下的经验会帮助你解决你在构建你的pipeline时遇到的困难。 在这第一篇文章里,我们将从头开始,看一看只用Docker时我们开发的初步的工作流。在接下来的文章中,我们将进一步介绍Docker-compose,最后介绍如何将Rancher应用到我们的工作流中。 为了为之后的工作铺平道路,假设接下来的事件都发生在一家SaaS提供商那里,我们曾经在SaaS提供商那里提供过长时间服务。仅为了这篇文章的撰写,我们姑且称这家SaaS提供商为Acme Business Company, Inc,即ABC。这项工程开始时,ABC正处在将大部分基于Java的微服务栈从裸机服务器上的本地部署迁移到运行在AWS上的Docker部署的最初阶段。这项工程的目标很常见:发布新功能时更少的前置时间(lead time)以及更可靠的部署服务。 为了达到该目标,软件的部署计划大致是这样的: 这个过程从代码的变更、提交、推送到git仓库开始。当代码推送到git仓库后,我们的CI系统会被告知运行单元测试。如果测试通过,就会编译代码并将结果作为产出物(artifact)存储起来。如果上一步成功了,就会触发下一步的工作,利用我们的代码产出物创建一个Docker镜像并将镜像推送到一个Docker私有注册表(private Docker registry)中。最后,我们将我们的新镜像部署到一个环境中。 要完成这个过程,如下几点是必须要有的: 一个源代码仓库。ABC已经将他们的代码存放在GitHub私有仓库上了。 一个持续集成和部署的工具。ABC已经在本地安装了Jenkins。 一个私有registry。我们部署了一个Docker registry容器,由Amazon S3支持。 一个主机运行Docker的环境。ABC拥有几个目标环境,每个目标环境都包含过渡性(staging)部署和生产部署。 这样去看的话,这个过程表面上简单,然而实际过程中会复杂一些。像许多其它公司一样,ABC曾经(现在仍然是)将开发团队和运营团队划分为不同的组织。当代码准备好部署时,会创建一个包含应用程序和目标环境详细信息的任务单(ticket)。这个任务单会被分配到运营团队,并将会在几周的部署窗口内执行。现在,我们已经不能清晰地看到一个持续部署和分发的方法了。 最开始,部署任务单可能看起来是这样的: 1 2 3 DEPLOY- 111 : App:JavaService1,branch "release/1.0.1" Environment:Production 部署过程是: 部署工程师用了一周时间在Jenkins上工作,对相关的工程执行”Build Now“,将分支名作为参数传递。之后弹出了一个被标记的Docker镜像。这个镜像被自动的推送到了注册表中。工程师选择了环境中的一台当前没有在负载均衡器中被激活的Docker主机。工程师登陆到这台主机并从注册表中获取新的版本。 1 dockerpullregistry.abc.net/javaservice1:release- 1.0 . 1 找到现存的容器。 1 dockerps 终止现存容器运行。 1 dockerstop[container_id] 开启一个新容器,这个容器必须拥有所有正确启动容器所需的标志。这些标志可以从之前运行的容器那里,主机上的shell历史,或者其它地方的文档借鉴。 1 dockerrun-d-p 8080 : 8080 …registry.abc.net/javaservice1:release- 1.0 . 1 连接这个服务并做一些手工测试确定服务正常工作。 1 curllocalhost: 8080 /api/v1/version 在生产维护窗口中,更新负载均衡器使其指向更新过的主机。 一旦通过验证,这个更新会被应用到环境中所有其它主机上,以防将来需要故障切换(failover)。 不可否认的是,这个部署过程并不怎么让人印象深刻,但这是通往持续部署伟大的第一步。这里有好多地方仍可改进,但我们先考虑一下这么做的优点: 运营工程师有一套部署的方案,并且每个应用的部署都使用相同的步骤。在Docker运行那一步中需要为每个服务查找参数,但是大体步骤总是相同的:Docker pull、Docker stop、Docker run。这个过程非常简单,而且很难忘掉其中一步。 当环境中最少有两台主机时,我们便拥有了一个可管理的蓝绿部署(blue-green deployment)。一个生产窗口只是简单地从负载均衡器配置转换过来。这个生产窗口拥有明显且快速的回滚方法。当部署变得更加动态时,升级、回滚以及发现后端服务器变得愈发困难,需要更多地协调工作。因为部署是手动的,蓝绿部署代价是最小的,并且同样能提供优于就地升级的主要优点。 好吧,现在看一看痛点: 重复输入相同的命令。或者更准确地说,重复地在bash命令行里敲击输入。解决这一点很简单:使用自动化技术!有很多工具可以帮助你启动Docker容器。对于运营工程师,最明显的解决方案是将重复的逻辑包装成bash脚本,这样只需一条命令就可以执行相应逻辑。如果你将自己称作一个开发-运营(devops)工程师,你可能会去使用Ansible、Puppet、Chef或者SaltStack。编写脚本或剧本(playbooks)很简单,但是这里仍有几个问题需要说明:部署逻辑到底放在那里?你怎样追踪每个服务的不同参数?这些问题将带领我们进入下一点。 即便一个运营工程师拥有超能力,在办公室工作一整天后的深夜里仍能避免拼写错误,并且清晰的思考,他也不会知道有一个服务正在监听一个不同的端口并且需要改变Docker端口参数。问题的症结在于开发者确实了解应用运行的详细信息(但愿如此),但是这些信息需要被传递给运营团队。很多时候,运营逻辑放在另外的代码仓库中或这根本没有代码仓库。这种情况下保持应用相关部署逻辑的同步会变得困难。由于这个原因,一个很好的做法是将你的部署逻辑只提交到包含你的Dockerfile的代码仓库。如果在一些情况下无法做到这点,有一些方法可以使这么做可行(更多细节将在稍后谈到)。把细节信息提交到某处是重要的。代码要比部署任务单好,虽然在一些人的脑海中始终认为部署任务单更好。 可见性。对一个容器进行一个故障检测须要登陆主机并且运行相应命令。在现实中,这就意味着登陆许多主机然后运行“docker ps”和“docker logs –tail=100”的命令组合。有很多解决方案可以做到集中登陆。如果你有时间的话,还是相当值得设置成集中登陆的。我们发现,通常情况下我们缺少的能力是查看哪些容器运行在那些主机上的。这对于开发者而言是个问题。开发者想要知道什么版本被部署在怎样的范围内。对于运营人员来说,这也是个主要问题。他们须要捕获到要进行升级或故障检测的容器。 基于以上的情况,我们开始做出一些改变,解决这些痛点。 第一个改进是写一个bash脚本将部署中相同的步骤包装起来。一个简单的包装脚本可以是这样的: 1 2 3 4 5 6 !/bin/bash APPLICATION=$ 1 VERSION=$ 2 dockerpull "registry.abc.net/${APPLICATION}:${VERSION}" dockerrm-f$APPLICATION dockerrun-d--name "${APPLICATION}" "registry.abc.net/${APPLICATION}:${VERSION}" 这样做行得通,但仅对于最简单的容器而言,也就是那种用户不需要连接到的容器。为了能够实现主机端口映射和卷挂载(volume mounts),我们须要增加应用程序特定的逻辑。这里给出一个使用蛮力实现的方法: 1 2 3 4 5 6 7 8 9 10 11 12 13 APPLICATION=$ 1 VERSION=$ 2 case "$APPLICATION" in java-service- 1 ) EXTRA_ARGS= "-p8080:8080" ;; java-service- 2 ) EXTRA_ARGS= "-p8888:8888--privileged" ;; *) EXTRA_ARGS= "" ;; esac dockerpull "registry.abc.net/${APPLICATION}:${VERSION}" dockerstop$APPLICATION dockerrun-d--name "${APPLICATION}" $EXTRA_ARGS "registry.abc.net/${APPLICATION}:${VERSION}" 现在这段脚本被安装在了每一台Docker主机上以帮助部署。运营工程师会登陆到主机并传递必要的参数,之后脚本会完成剩下的工作。部署时的工作被简化了,工程师的需要做的事情变少了。然而将部署代码化的问题仍然存在。我们回到过去,把它变成一个关于向一个共同脚本提交改变并且将这些改变分发到主机上的问题。通常来说,这样做很值得。将代码提交到仓库会给诸如代码审查、测试、改变历史以及可重复性带来巨大的好处。在关键时刻,你要考虑的事情越少越好。 理想状况下,一个应用的相关部署细节和应用本身应当存在于同一个源代码仓库中。有很多原因导致现实情况不是这样,最突出的原因是开发人员可能会反对将运营相关的东西放入他们的代码仓库中。尤其对于一个用于部署的bash脚本,这种情况更可能发生,当然Dockerfile文件本身也经常如此。 这变成了一个文化问题并且只要有可能的话就值得被解决。尽管为你的部署代码维持两个分开的仓库确实是可行的,但是你将不得不耗费额外的精力保持两个仓库的同步。本篇文章当然会努力达到更好的效果,即便实现起来更困难。在ABC,Dockerfiles最开始在一个专门的仓库中,每个工程都对应一个文件夹,部署脚本存在于它自己的仓库中。 Dockerfiles仓库拥有一个工作副本,保存在Jenkins主机上一个熟知的地址中(就比如是‘/opt/abc/Dockerfiles’)。为了为一个应用创建Docker镜像,Jenkins会搜索Dockerfile的路径,在运行”docker build“前将Dockerfile和伴随的脚本复制进来。由于Dockerfile总是在掌控中,你便可能发现你是否处在Dockerfile超前(或落后)应用配置的状态,虽然实际中大部分时候都会处在正常状态。这是来自Jenkins构建逻辑的一段摘录: 1 2 3 4 5 6 7 8 9 if [-fdocker/Dockerfile];then docker_dir=Docker elif[-f/opt/abc/dockerfiles/$APPLICATION/Dockerfile];then docker_dir=/opt/abc/dockerfiles/$APPLICATION else echo "Nodockerfiles.Can’tcontinue!" exit 1 if dockerbuild-t$APPLICATION:$VERSION$docker_dir 随着时间的推移,Dockerfiles以及支持脚本会被迁移到应用程序的源码仓库中。由于Jenkins最开始已经查看了本地的仓库,pipeline的构建不再需要任何变化。在迁移了第一个服务后,仓库的结构大致是这样的: 我们使用分离的仓库时遇到的一个问题是,如果应用源码或打包逻辑任意一个发生改变,Jenkins就会触发应用的重建。由于Dockerfiles仓库包含了许多项目的代码,当改变发生时我们不想触发所有的仓库重建。解决方法是:使用在Jenkins Git插件中一个很隐蔽的选项,叫做Included Regions。当配置完成后,Jenkins将一个变化引起的重建隔离在仓库的某个特定子集里面。这允许我们将所有的Dockerfiles放在一个仓库里,并且仍然能做到当一个改变发生时只会触发特定的构建(与当改变发生在仓库里特定的目录时构建所有的镜像相比)。 关于这个初步的工作流的另一个方面是部署工程师必须在部署前强制构建一个应用镜像。这将导致额外的延迟,尤其是构建存在问题并且开发人员需要参与其中的时候。为了减少这种延迟,并为更加持续的部署铺平道路,我们开始为熟知分支中的每一个提交构建Docker镜像。这要求每一个镜像有一个独一无二的版本标识符,而如果我们仅仅依赖官方的应用版本字符串往往不能满足这一点。最终,我们使用官方版本字符串、提交次数和提交sha码的组合作为版本标识符。 1 2 3 commit_count=$(gitrev-list--countHEAD) commit_short=$(gitrev-parse-- short HEAD) version_string= "${version}-${commit_count}-${commit_short}" 这样得到的版本字符串看起来是这样的:1.0.1-22-7e56158 在结束pipeline的Docker file部分的讨论之前,还有一些参数值得提及。如果我们不会在生产中操作大量的容器,我们很少用到这些参数。但是,它们被证明有助于我们维护Docker集群的线上运行。 重启策略(Restart Policy)-一个重启策略允许你指定当一个容器退出时,每个容器采取什么动作。尽管这个可以被用作应用错误(application panic)时的恢复或当依赖上线时保持容器再次尝试连接,但对运营人员来说真正的好处是在Docker守护进程(daemon)或者主机重启后的自动恢复。从长远来看,你将希望实现一个适当的调度程序(scheduler),它能够在新主机上重启失败的容器。在那天到来之前,节省一些工作,设置一个重启策略吧。在现阶段的ABC中,我们将这项参数默认为“–restart always”,这将会使容器始终重启。简单地拥有一个重启策略就会使计划的(和非计划的)主机重启变得轻松得多。 资源约束(Resource Constraints)-使用运行时的资源约束,你可以设置容器允许消耗的最大内存和CPU。它不会把你从一般的主机过载(over-subscription)中拯救出来,但是它可以抑制住内存泄漏和失控的容器。我们先对容器应用一个充足的内存限制(例如:–memory=”8g”) 。我们知道当内存增长时这样会产生问题。尽管拥有一个硬性限制意味着应用最终会达到内存不足(Out-of-Memory)的状态并产生错误(panic),但是主机和其它容器会保持正确运行。 结合重启策略和资源约束会给你的集群带来更好的稳定性,与此同时最小化失败的影响,缩短恢复的时间。这种类型的安全防护可以让你和开发人员一起专注于“起火”的根本原因,而不是忙于应付不断扩大的火势。 简而言之,我们从一个基础的构建pipeline,即从我们的源码仓库中创建被标记的Docker镜像开始。从使用Docker CLI部署容器一路到使用脚本和代码中定义的参数部署容器。我们也涉及了如何管理我们的部署代码,并且强调了几个帮助运营人员保持服务上线和运行的Docker参数。 此时此刻,在我们的构建pipeline和部署步骤之间仍然存在空缺。部署工程师会通过登入一个服务器并运行部署脚本的方法填补这个空缺。尽管较我们刚开始时有所改进,但仍然有进一步提高自动化水平的空间。所有的部署逻辑都集中在单一的脚本内,当开发者需要安装脚本以及应付它的复杂性时,会使本地测试会变得困难得多。此时此刻,我们的部署脚本也包含了通过环境变量处理任何环境特定信息的方法。追踪一个服务设置的环境变量以及增加新的环境变量是乏味且容易出错的。 在下一篇文章中,我们将看一看怎样通过解构(deconstructing)共同的包装脚本解决这些痛点,并使部署逻辑向使用Docker Compose的应用更近一步。 您也可以下载免费的电子书《Continuous Integration and Deployment with Docker and Rancher》,这本书讲解了如何利用容器帮助你完成整个CI/CD过程。 原文来源:Rancher Labs 本文转自 RancherLabs 51CTO博客,原文链接:http://blog.51cto.com/12462495/1933287

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

开源中国iOS客户端学习——(十四)使用EGOImageLoading异步加载图片

EGOImageLoading 是一个用的比较多的异步加载图片的第三方类库,简化开发过程,我们直接传入图片的url,这个类库就会自动帮我们异步加载和缓存工作;当从网上获取图片时,如果网速慢图片短时间内不能下载下来,可以先用一张本地的图片代替显示,还可以进行其他操作,让图片下载完成后自动替换占位图片而不影响用户体验; EGOImageLoading 的GitHub 下载地址 https://github.com/enormego/EGOImageLoading GitHub上下载下来的类库会有一个Demo,如果运行出错说明缺少EGOCache类,在https://github.com/enormego/EGOCache添加道工程之中,或者直接在附件上下载 首先还是来分析一下开源中国iOS客户端如何使用这个第三方类库 在我搜索客户端中哪些类使用了这个类库的时候和预期的并不一样,在工程中有很多地方需要使用到图片的异步加载,而使用EGOImageLoading类库加载只有三个地方,也可以说是两个地方 一是在显示个人资料加载个人图片,显示个人信息时候使用的。 二个是显示你的粉丝或者你关注的人,想查看TA的资料的时候 在MyView类和UserView2类中,使用方法一样 声明一个 EGOImageView管理图片的异步加载 1 @property (strong,nonatomic) EGOImageView * egoImgView; 在ViewDidLoad方法中 1 2 3 4 5 6 7 // 初始化 self.egoImgView = [[EGOImageView alloc] initWithFrame:CGRectMake(15, 4, 70, 70)]; // 占位图片 self.egoImgView.image = [UIImage imageNamed:@ "big_avatar_loading.png" ]; // 设置图片圆角弧度 egoImgView.layer.cornerRadius = 10.0f; [self.view addSubview:self.egoImgView]; 然后就是在reload()方法中图片加载处理,先从网络解析获取图片的url资源,如果未获取到图片url仍然显示占位图片,如果获取到了就将占位图片更换为解析获取的图片 1 2 3 4 5 6 7 8 9 10 //头像 NSString *portrait_str = [TBXML textForElement:portrait]; if ([portrait_str isEqualToString:@ "" ]) { self.egoImgView.image = [UIImage imageNamed:@ "big_avatar.png" ]; } else { self.egoImgView.imageURL = [NSURL URLWithString:portrait_str]; } 以上就是使用EGOImageLoading 类库进行图片的异步加载; 以下是一个使用EGOImageLoading 类库进行图片异步加载的示例Demo,下载见附件 在开源中国iOS 客户端的问答、动弹、我的三个视图也涉及到图片的显示加载问题,刚开始误以为使用EGOImageLoading 类库异步加载图片,而实际上是一个延迟加载,先用占位图片显示,然后使用IconDownloader类库从服务器端将图片下载到本地缓存,在进行加载显示; 本文转自新风作浪 51CTO博客,原文链接:http://blog.51cto.com/duxinfeng/1214170,如需转载请自行联系原作者

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

Hadoop Hive概念学习系列之hive的数据压缩(七)

Hive文件存储格式包括以下几类: 1、TEXTFILE 2、SEQUENCEFILE 3、RCFILE 4、ORCFILE 其中TEXTFILE为默认格式,建表时不指定默认为这个格式,导入数据时会直接把数据文件拷贝到hdfs上不进行处理。 SEQUENCEFILE,RCFILE,ORCFILE格式的表不能直接从本地文件导入数据,数据要先导入到textfile格式的表中, 然后再从表中用insert导入SequenceFile,RCFile,ORCFile表中。 更多用法,一定要去看官网啊!!! https://cwiki.apache.org/confluence/display/Hive/LanguageManual+DDL 一、TEXTFILE 格式默认格式,数据不做压缩,磁盘开销大,数据解析开销大。 可结合Gzip、Bzip2使用(系统自动检查,执行查询时自动解压),但使用这种方式,Hive不会对数据进行切分, 从而无法对数据进行并行操作。 示例: create table if not exists textfile_table( site string, url string, pv bigint, label string) row format delimited fields terminated by '\t' stored as textfile; 插入数据操作: Hive> Hive.exec.compress.output=true; Hive> set mapred.output.compress=true; Hive> set mapred.output.compression.codec=org.apache.hadoop.io.compress.GzipCodec; Hive> set io.compression.codecs=org.apache.hadoop.io.compress.GzipCodec; Hive> insert overwrite table textfile_table select * from textfile_table; 二、SEQUENCEFILE 格式 SequenceFile是Hadoop API提供的一种二进制文件支持,其具有使用方便、可分割、可压缩的特点。 SequenceFile支持三种压缩选择:NONE,RECORD,BLOCK。Record压缩率低,一般建议使用BLOCK压缩。 示例: create table if not exists seqfile_table( site string, url string, pv bigint, label string) row format delimited fields terminated by '\t' stored as sequencefile; 插入数据操作: Hive> set Hive.exec.compress.output=true; Hive> set mapred.output.compress=true; Hive> set mapred.output.compression.codec=org.apache.hadoop.io.compress.GzipCodec; Hive> set io.compression.codecs=org.apache.hadoop.io.compress.GzipCodec; Hive> SET mapred.output.compression.type=BLOCK; Hive> insert overwrite table seqfile_table select * from textfile_table; 三、RCFILE 文件格式 RCFILE是一种行列存储相结合的存储方式。首先,其将数据按行分块,保证同一个record在一个块上,避免读一个记录需要读取多个block。 其次,块数据列式存储,有利于数据压缩和快速的列存取。 RCFILE文件示例: create table if not exists rcfile_table( site string, url string, pv bigint, label string) row format delimited fields terminated by '\t' stored as rcfile; 插入数据操作: Hive> set Hive.exec.compress.output=true; Hive> set mapred.output.compress=true; Hive> set mapred.output.compression.codec=org.apache.hadoop.io.compress.GzipCodec; Hive> set io.compression.codecs=org.apache.hadoop.io.compress.GzipCodec; Hive> insert overwrite table rcfile_table select * from textfile_table; 四、ORCFILE() 以后补充 五、再看TEXTFILE、SEQUENCEFILE、RCFILE三种文件的存储情况: [hadoop@master ~]$ hadoop dfs -dus /user/Hive/warehouse/* hdfs://master:9000/user/Hive/warehouse/hbase_table_1 0 hdfs://master:9000/user/Hive/warehouse/hbase_table_2 0 hdfs://master:9000/user/Hive/warehouse/orcfile_table 0 hdfs://master:9000/user/Hive/warehouse/rcfile_table 102638073 hdfs://master:9000/user/Hive/warehouse/seqfile_table 112497695 hdfs://master:9000/user/Hive/warehouse/testfile_table 536799616 hdfs://master:9000/user/Hive/warehouse/textfile_table 107308067 [hadoop@singlehadoop ~]$ hadoop dfs -ls /user/Hive/warehouse/*/ -rw-r--r-- 2 hadoop supergroup 51328177 2014-03-20 00:42 /user/Hive/warehouse/rcfile_table/000000_0 -rw-r--r-- 2 hadoop supergroup 51309896 2014-03-20 00:43 /user/Hive/warehouse/rcfile_table/000001_0 -rw-r--r-- 2 hadoop supergroup 56263711 2014-03-20 01:20 /user/Hive/warehouse/seqfile_table/000000_0 -rw-r--r-- 2 hadoop supergroup 56233984 2014-03-20 01:21 /user/Hive/warehouse/seqfile_table/000001_0 -rw-r--r-- 2 hadoop supergroup 536799616 2014-03-19 23:15 /user/Hive/warehouse/testfile_table/weibo.txt -rw-r--r-- 2 hadoop supergroup 53659758 2014-03-19 23:24 /user/Hive/warehouse/textfile_table/000000_0.gz -rw-r--r-- 2 hadoop supergroup 53648309 2014-03-19 23:26 /user/Hive/warehouse/textfile_table/000001_1.gz 总结: 相比TEXTFILE和SEQUENCEFILE,RCFILE由于列式存储方式,数据加载时性能消耗较大,但是具有较好的压缩比和查询响应。 数据仓库的特点是一次写入、多次读取,因此,整体来看,RCFILE相比其余两种格式具有较明显的优势。 以下,本文转自于。http://blog.csdn.net/cnbird2008/article/details/9182869 Hive数据压缩 本文介绍Hadoop系统中Hive数据压缩方案的比较结果及具体压缩方法。 一、压缩方案比较 关于Hadoop HDFS文件的压缩格式选择,我们通过多个真实的Track数据做测试,得出结论如下: 1.系统的默认压缩编码方式 DefaultCodec 无论在压缩性能上还是压缩比上,都优于GZIP 压缩编码。这一点与网上的一些观点不大一致,网上不少人认为GZIP的压缩比要高一些,估计和Cloudera的封装及我们Track的数据类型有关。 2. Hive文件的RCFile 的在压缩比,压缩效率,及查询效率上都优于SEQENCE FILE (包括RECORD, BLOCK 级别) 。 3. 所有压缩文件均可以正常解压为TEXT 文件,但比原始文件略大,可能是行列重组造成的。 关于压缩文件对于其他组件是适用性如下: 1. Pig 不支持任何形式的压缩文件。 2. Impala 目前支持SequenceFile的压缩格式,但还不支持RCFile的压缩格式。 综上所述: 从压缩及查询的空间和时间性能上来说,DefaultCodeC + RCFile的压缩方式均为最优,但使用该方式,会使得Pig 和Impala 无法使用(Impala的不兼容不确定是否是暂时的)。 而DefaultCodeC+ SequenceFile 在压缩比,查询性能上略差于RCFile (压缩比约 6:5), 但可以支持 Impala实时查询。 推荐方案: 采用RCFile 方式压缩历史数据。FackBook全部hive表都用RCFile存数据。 二、局部压缩方法 只需要两步: 1.创建表时指定压缩方式,默认不压缩,以下为示例: create external table track_hist( id bigint, url string, referer string, keyword string, type int, gu_idstring, …/*此处省略中间部分字段*/ …, string,ext_field10 string) partitioned by (ds string)stored asRCFilelocation '/data/share/track_histk' ; 2. 插入数据是设定立即压缩 SET hive.exec.compress.output=true; insert overwrite table track_histpartition(ds='2013-01-01') select id,url, …/*此处省略中间部分字段*/ …, ext_field10 fromtrackinfo where ds='2013-01-01'; 三、全局方式,修改属性文件 在hive-site.xml中设置: <property> <name>hive.default.fileformat</name> <value>RCFile</value> <description>Default file format for CREATE TABLE statement.Options are TextFile and SequenceFile. Users can explicitly say CREAT E TABLE ... STORED AS&lt;TEXTFILE|SEQUENCEFILE&gt; to override</description> </property> <property> <name>hive.exec.compress.output</name> <value>true</value> <description> This controls whether the final outputs of a query(to a local/hdfs file or a hive table) is compressed. The compres sion codec and other options are determinedfrom hadoop config variables mapred.output.compress* </description> 四、注意事项 1、Map阶段输出不进行压缩 2、对输出文本进行处理时不压缩 本文转自大数据躺过的坑博客园博客,原文链接:http://www.cnblogs.com/zlslch/p/6103760.html,如需转载请自行联系原作者

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

英特尔推出全新自主学习芯片加速人工智能发展

Michael C. Mayberry博士 Michael C. Mayberry博士 英特尔公司副总裁兼英特尔研究院院长 Michael C. Mayberry博士现任英特尔公司副总裁兼英特尔研究院院长,负责英特尔在计算和通信领域的全球研究工作。此外,他还领导公司研究委员会,负责推动英特尔大学定向研究项目的资源调配与优先排序。 自从1984年加入英特尔公司并担任制程集成工程师以来,Mayberry博士曾在公司的多个职位任职。作为加州技术开发团队的成员,他开发了EPROM、闪存和逻辑晶圆制造工艺。1994年,他加入晶圆测试技术开发团队,负责英特尔微处理器测试流程的路线图制定与开发工作。2005年,他进入组件研究团队,负责为英特尔的技术开发部门提供未来制程的选项。 Mayberry博士于1983年在加州大学伯克利分校获得物理化学博士学位,并于

资源下载

更多资源
腾讯云软件源

腾讯云软件源

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

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应用均可从中受益。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册