一 持久存储

1.1 持久存储概述

默认情况下,运行容器使用容器内的临时存储。Pods由一个或多个容器组成,这些容器一起部署,共享相同的存储和其他资源,可以在任何时候创建、启动、停止或销毁。使用临时存储意味着,当容器停止时,写入容器内的文件系统的数据将丢失。
当容器在停止时也需要持久的保存数据时,OpenShift使用Kubernetes持久卷(PVs)为pod提供持久存储。

1.2 持久存储场景

通常用于数据库,启动一个数据库的pod时提供的默认临时存储。如果销毁并重新创建数据库pod,则销毁临时存储并丢失数据。如果使用持久存储,则数据库将数据存储到pod外部的持久卷中。如果销毁并重新创建pod,数据库应用程序将继续访问存储数据的相同外部存储。

1.3 持久存储相关概念

持久卷(PV)是OpenShift资源,它只由OpenShift管理员创建和销毁。持久卷资源表示所有OpenShift节点都可以访问的网络连接存储。
持久性存储组件:
OCP使用Kubernetes持久卷(PV)技术,允许管理员为集群提供持久性存储。开发人员使用持久性卷声明(PVC)请求PV资源,而不需要了解具体的底层存储基础设施。
Persistent Volume:PV是OpenShift集群中的资源,由PersistentVolume API对象定义,它表示集群中由管理员提供的现有网络存储的一部分。它是集群中的资源,就像节点是集群资源一样。PV的生命周期独立于使用PV的任何单独pod。
Persistent Volume Claim:pvc由PersistentVolumeClaim API对象定义,该对象表示开发人员对存储的请求。它与pod类似,pod消耗节点资源,而pvc消耗PV资源。

1.4 持久存储插件

卷是挂载的文件系统,对pods及其容器可用,并且可以由许多本地或网络连接的存储进行备份。OpenShift使用插件来支持以下不同的后端用于持久存储:
  • NFS
  • GlusterFS
  • OpenStack Cinder
  • Ceph RBD
  • AWS Elastic Block Store (EBS)
  • GCE Persistent Disk
  • iSCSI
  • Fibre Channel
  • Azure Disk and Azure File
  • FlexVolume (allows for the extension of storage back-ends that do not have a built-in plug-in)
  • VMWare vSphere
  • Dynamic Provisioning and Creating Storage Classes
  • Volume Security
  • Selector-Label Volume Binding

1.5 PV访问模式

PV可以以resource provider的任何方式挂载在主机上,provider具有不同的功能,并且每个持久卷的访问模式都设置为该特定卷支持的特定模式。例如,NFS可以支持多个读/写客户端,但是特定的NFS PV可以在服务器上作为只读导出。
每个PV接收自己的一组访问模式,描述特定的持久卷的功能。
访问模式见下表:



访问模式

CLI缩写

描述
ReadWriteOnce
RWO
卷可以被单个节点挂载为读/写
ReadOnlyMany
ROX
卷可以由许多节点以只读方式挂载
ReadWriteMany
RWX
卷可以被许多节点挂载为读/写
PV claims与具有类似访问模式的卷匹配。唯一的两个匹配标准是访问模式和大小。claim的访问模式表示请求。因此,可以授予用户更大的访问权限,但绝不能减少访问权限。例如,如果一个claim请求RWO,但是惟一可用的卷是NFS PV (RWO+ROX+RWX),那么claim将匹配NFS,因为它支持RWO。
所有具有相同模式的卷都被分组,然后按大小(从最小到最大)排序。
master上负责将PV绑定到PVC上的service接收具有匹配模式的组,并在每个组上迭代(按大小顺序),直到一个大小匹配为止,然后将PV绑定到PVC上。

1.6 Persistent Volume Storage Classes

PV Claims可以通过在storageClassName属性中指定它的名称来选择性地请求特定的存储类。只有与PVC具有相同存储类名称的请求类的pv才能绑定到PVC。
集群管理员可以为所有PVC设置一个默认存储类,或者配置动态供应程序来服务一个或多个存储类,这些存储类将匹配可用PVC中的规范。

1.7 创建pv和PVC资源

pv是集群中的资源,pvc是对这些资源的请求,也充当对资源的claim检查。pv与PVCs的相互作用具有以下生命周期:
  • 创建持久卷
集群管理员创建任意数量的pv,这些pv表示集群用户可以通过OpenShift API使用的实际存储的信息。
  • 定义持久卷声明
用户创建具有特定存储量、特定访问模式和可选存储类的PVC。master监视新的pvc,要么找到匹配的PV,要么等待存储类创建一个供应程序,然后将它们绑定在一起。
  • 使用持久存储
Pods使用claims作为卷。集群检查查找绑定卷的声明,并为pod绑定该卷。对于那些支持多种访问模式的卷,用户在将其声明用作pod中的卷时指定需要哪种模式。
一旦用户有了一个claim,并且该claim被绑定,绑定的PV就属于用户,使用过程中该PV都属于该用户。用户通过在pod的Volume中包含一个持久的卷claim来调度pod并访问其声明的pv。

1.8 使用NFS的PV

OpenShift使用随机uid运行容器,因此将Linux用户从OpenShift节点映射到NFS服务器上的用户并不能正常工作。作为OpenShift pv使用的NFS共享必须遵从如下配置:
  • 属于nfsnobody用户和组。
  • 拥有rwx------权限(即0700)。
  • 使用all_squash选项
示例配置:
/var/export/vol *(rw,async,all_squash)
其他NFS export选项,例如sync和async,与OpenShift无关。如果使用任何一个选项,OpenShift都可以工作。但是,在高延迟环境中,添加async选项可以加快NFS共享的写操作(例如,将image push到仓库的场景)。
使用async选项更快,因为NFS服务器在处理请求时立即响应客户端,而不需要等待数据写到磁盘。
当使用sync选项时,则相反,NFS服务器只在数据写到磁盘之后才响应客户端。
注意:NFS共享文件系统大小和用户配额对OpenShift没有影响。PV大小在PV资源定义中指定。如果实际文件系统更小,则PV被创建并绑定。如果PV更大,OpenShift不会将使用的空间限制为指定的PV大小,并且允许容器使用文件系统上的所有空闲空间。OpenShift自身提供了存储配额和存储位置限制,可用于控制项目中的资源分配。
默认的SELinux策略不允许容器访问NFS共享。必须在每个OpenShift实例节点中更改策略,方法是将virt_use_nfs和virt_sandbox_use_nfs变量设置为true。
  1 # setsebool -P virt_use_nfs=true
2 # setsebool -P virt_sandbox_use_nfs=true

1.9 NFS回收政策

NFS支持OpenShift的Recyclable插件,根据在每个持久卷上设置的策略处理自动执行回收任务。
默认情况下,持久卷被设置为Retain。Retain reclaim策略允许手动回收资源。当删除pv claim时,持久卷仍然存在,并且认为该卷已被释放。但它还不能用于另一个claim,因为来自前一个claim的数据仍然保留在卷上。此时管理员可以手动回收卷。
NFS卷及其回收策略设置为Recycle,表示在从claim中释放后将被清除。例如,当将NFS回收策略设置为Recycle后,在删除用户绑定到该卷的pv claim之后,会在该卷上运行rm -rf命令。在它被回收之后,NFS卷可以直接绑定到一个新的pv claim。

1.10 Supplemental group

Supplemental group是常规的Linux组。当一个进程在Linux中运行时,它有一个UID、一个GID和一个或多个Supplemental group。可以为容器的主进程设置这些属性。
Supplemental groupid通常用于控制对共享存储的访问,比如NFS和GlusterFS,而fsGroup用于控制对块存储(如Ceph的RBD活iSCSI)的访问。
OpenShift共享存储插件挂载卷,以便使挂载上的POSIX权限与目标存储上的权限匹配。例如,如果目标存储的所有者ID是1234,组ID是5678,那么宿主节点和容器中的挂载将具有相同的ID。因此,容器的主进程必须匹配一个或两个id,才能访问该卷。
  1 [root@node ~]# showmount -e
2 Export list for master.lab.example.com:
3 /var/export/nfs-demo *
4 [root@services ~]# cat /etc/exports.d/nfs-demo.conf
5 /var/export/nfs-demo
6 ...
7 [root@services ~]# ls -lZ /var/export -d
8 drwx------. 10000000 650000 unconfined_u:object_r:usr_t:s0 /var/export/nfs-demo
图上示例,UID 10000000和组650000可以访问/var/export/nfs-demo export。通常,容器不应该作为root用户运行。在这个NFS示例中,如果容器不是作为UID 10000000运行的,并且不是组650000的成员,那么这些容器就不能访问NFS export。

1.11 通过fsgroup使用块存储

fsGroup定义了pod的“file-system group”ID,该ID被添加到容器的supplemental group中。supplemental group ID应用于共享存储,而fsGroup ID用于块存储。
块存储,如Ceph RBD、iSCSI和各种类型的云存储,通常专用于单个pod。与共享存储不同,块存储由pod接管,这意味着pod(或image)定义中提供的用户和组id应用于实际的物理块设备,块存储通常不共享。

1.12 SELINUX和卷security

除了SCC之外,所有预定义的安全上下文约束都将seLinuxContext设置为MustRunAs。最可能匹配pod需求的SCC迫使pod使用SELinux策略。pod使用的SELinux策略可以在pod本身、image、SCC或project(提供默认值)中定义。
SELinux标签可以在pod的securityContext中定义。,并支持user、role、type和level标签。

1.13 ELinuxContext选项

  • MustRunAs
如果不使用预先分配的值,则要求配置seLinuxOptions。使用seLinuxOptions作为默认值,从而针对seLinuxOptions验证。
  • RunAsAny
没有提供默认,允许指定任何seLinuxOptions。

二 持久卷练习

2.1 前置准备

准备完整的OpenShift集群,参考《003.OpenShift网络》2.1。

2.2 本练习准备

  1 [student@workstation ~]$ lab deploy-volume setup

2.3 配置NFS

本实验不详解NFS的配置和创建,直接使用/root/DO280/labs/deploy-volume/config-nfs.sh脚本实现,具体脚本内容可通过以下方式查看。
同时NFS由services节点提供。
  1 [root@services ~]# less -FiX /root/DO280/labs/deploy-volume/config-nfs.sh
2 [root@services ~]# /root/DO280/labs/deploy-volume/config-nfs.sh #创建NFS
3 Export directory /var/export/dbvol created.
4 [root@services ~]# showmount -e #确认验证

2.4 node节点挂载NFS

  1 [root@node1 ~]# mount -t nfs services.lab.example.com:/var/export/dbvol /mnt
2 [root@node1 ~]# mount | grep /mnt
3 [root@node1 ~]# ll -a /mnt/ #检查相关权限
  1 [root@node1 ~]# umount /mnt/		#卸载
提示:建议node2也做以上挂载测试,测试完成后建议下载,NFS共享在OpenShift需要的时候会自动挂载。



2.5 创建持久卷

  1 [student@workstation ~]$ oc login -u admin -p redhat https://master.lab.example.com
2 [student@workstation ~]$ less -FiX /home/student/DO280/labs/deploy-volume/mysqldb-volume.yml
3 apiVersion: v1
4 kind: PersistentVolume
5 metadata:
6 name: mysqldb-volume
7 spec:
8 capacity:
9 storage: 3Gi
10 accessModes:
11 - ReadWriteMany
12 nfs:
13 path: /var/export/dbvol
14 server: services.lab.example.com
15 persistentVolumeReclaimPolicy: Recycle
16 [student@workstation ~]$ oc create -f /home/student/DO280/labs/deploy-volume/mysqldb-volume.yml
17 [student@workstation ~]$ oc get pv #查看PV
18 NAME CAPACITYACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
19 mysqldb-volume 3Gi RWX Recycle Available 1m

2.6 创建项目

  1 [student@workstation ~]$ oc login -u developer -p redhat https://master.lab.example.com
2 [student@workstation ~]$ oc new-project persistent-storage

2.7 部署应用

  1 [student@workstation ~]$ oc new-app --name=mysqldb \
2 --docker-image=registry.lab.example.com/rhscl/mysql-57-rhel7 \
3 -e MYSQL_USER=ose \
4 -e MYSQL_PASSWORD=openshift \
5 -e MYSQL_DATABASE=quotes
6 [student@workstation ~]$ oc status #确认验证
7 In project persistent-storage on server https://master.lab.example.com:443
8
9
10 svc/mysqldb - 172.30.39.72:3306
11 dc/mysqldb deploys istag/mysqldb:latest
12 deployment #1 deployed 58 seconds ago - 1 pod

2.8 配置持久卷

  1 [student@workstation ~]$ oc describe pod mysqldb | grep -A2 'Volumes'	#查看当前pod的Volume
2 Volumes:
3 mysqldb-volume-1:
4 Type: EmptyDir (a temporary directory that shares a pod's lifetime)
5 [student@workstation ~]$ oc set volumes dc mysqldb \
6 --add --overwrite --name=mysqldb-volume-1 -t pvc \
7 --claim-name=mysqldb-pvclaim \
8 --claim-size=3Gi \
9 --claim-mode='ReadWriteMany' #修改dc并创建PVC
10 [student@workstation ~]$ oc describe pod mysqldb | grep -E -A 2 'Volumes|ClaimName' #查看验证
  1 [student@workstation ~]$ oc get pvc		#查看PVC
2 NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
3 mysqldb-pvclaim Bound mysqldb-volume 3Gi RWX 2m

2.9 端口转发

  1 [student@workstation ~]$ oc get pod
2 NAME READY STATUS RESTARTS AGE
3 mysqldb-2-r7wz8 1/1 Running 0 4m
4 [student@workstation ~]$ oc port-forward mysqldb-2-r7wz8 3306:3306

2.10 测试数据库

  1 [student@workstation ~]$ mysql -h127.0.0.1 -uose -popenshift \
2 quotes < /home/student/DO280/labs/deploy-volume/quote.sql #填充数据测试
3 [student@workstation ~]$ mysql -h127.0.0.1 -uose -popenshift \
4 quotes -e "select count(*) from quote;" #确认填充完成
5 [student@workstation ~]$ ssh root@services ls -la /var/export/dbvol #查看NFS服务端数据
6 ……
7 drwxr-x---. 2 nfsnobody nfsnobody 54 Jul 21 23:43 quotes
8 ……
9 [student@workstation ~]$ ssh root@services ls -la /var/export/dbvol/quotes
10 total 116
11 drwxr-x---. 2 nfsnobody nfsnobody 54 Jul 21 23:43 .
12 drwx------. 6 nfsnobody nfsnobody 4096 Jul 21 23:39 ..
13 -rw-r-----. 1 nfsnobody nfsnobody 65 Jul 21 23:39 db.opt
14 -rw-r-----. 1 nfsnobody nfsnobody 8584 Jul 21 23:43 quote.frm
15 -rw-r-----. 1 nfsnobody nfsnobody 98304 Jul 21 23:44 quote.ibd

2.11 删除PV

  1 [student@workstation ~]$ oc delete project persistent-storage	#删除项目
2 project "persistent-storage" deleted
3 [student@workstation ~]$ oc delete pv mysqldb-volume #删除PV
4 persistentvolume "mysqldb-volume" deleted

2.12 验证持久性

删除PV后验证数据是否会长期保留。
  1 [student@workstation ~]$ ssh root@services ls -la /var/export/dbvol
2 ……
3 drwxr-x---. 2 nfsnobody nfsnobody 54 Jul 21 23:43 quotes
4 ……
5 [student@workstation ~]$ ssh root@services rm -rf /var/export/dbvol/* #使用rm才可以彻底删除

三 私有仓库持久存储

3.1 创建私有仓库持久卷

OCP内部仓库是source-to-image(S2I)流程的一个重要组件,该流程用于从应用程序源代码创建pod。S2I流程的最终输出是一个容器image,它被推送到OCP内部仓库,然后可以用于部署。
在生产环境中,通常建议为内部仓库提供一个持久性存储。否则,在重新创建registry pod之后,S2I创建的pod可能无法启动。例如,在master节点重新启动之后。
OpenShift安装程序配置并启动一个默认的持久仓库,该仓库使用NFS共享,由Inventory文件中的openshift_hosted_registry_storage_*变量定义。在生产环境中,Red Hat建议由外部专用的存储提供持久性存储,该服务器配置为弹性和高可用性。
高级安装程序将NFS服务器配置为使用外部NFS服务器上的持久存储,在[NFS]字段中定义的一个NFS服务器的列表。该服务器与openshift_hosted_registry_storage*变量一起使用,以配置NFS服务器。
示例配置:
  1 [OSEv3:vars]
2 openshift_hosted_registry_storage_kind=nfs #定义OCP存储后端
3 openshift_hosted_registry_storage_access_modes=['ReadWriteMany'] #定义访问模式,默认为ReadWriteMany,表示允许多个节点以读写形式挂载
4 openshift_hosted_registry_storage_nfs_directory=/exports #定义NFS服务器上的NFS存储目录
5 openshift_hosted_registry_storage_nfs_options='*(rw,root_squash)' #定义存储卷的NFS选项。这些选项被添加到/etc/ exports.d/openshift-ansible.exports中。rw选项允许对NFS卷进行读写访问,root_squash选项阻止远程连接的根用户拥有root特权,并为nfsnobody分配用户ID
6 openshift_hosted_registry_storage_volume_name=registry #定义要用于持久仓库的NFS目录的名称
7 openshift_hosted_registry_storage_volume_size=40Gi #定义持久卷大小
8 ... output omitted ...
9 [nfs]
10 services.lab.example.com
在为持久仓库安装和配置存储之后,OpenShift在OpenShift项目中创建一个名为register-volume的持久卷。持久性卷的容量为40gb,并且根据定义设置了Retain策略。同时默认项目中的pvc调用pv。
  1 [student@workstation ~]$ oc describe pv registry-volume
2 Name: registry-volume #定义持久卷名
3 Labels: <none>
4 Annotations: pv.kubernetes.io/bound-by-controller=yes
5 StorageClass:
6 Status: Bound
7 Claim: default/registry-claim #定义使用持久卷的声明
8 Reclaim Policy: Retain #默认持久卷策略,具有Retain策略的卷在从其声明中释放后不会被擦除
9 Access Modes: RWX #定义持久卷的访问模式,由Ansible inventory文件的openshift_hosted_registry_storage_access_modes=['ReadWriteMany']变量定义
10 Capacity: 40Gi #定义持久卷的大小,由Ansible inventory文件的openshift_hosted_registry_storage_volume_size变量定义
11 Message:
12 Source: #定义存储后端的位置和NFS共享
13 Type: NFS (an NFS mount that lasts the lifetime of a pod)
14 Server: services.lab.example.com
15 Path: /exports/registry
16 ReadOnly: false
17 Events: <none>
运行以下命令,确认OpenShift内部仓库已配置registry-volume作为默认的PersistentVolumeClaim。
  1 [user@demo ~] oc describe dc/docker-registry | grep -A4 Volumes
2 Volumes:
3 registry-storage:
4 Type: PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
5 ClaimName: registry-claim
6 ReadOnly: false
OCP内部仓库将image和metadata存储为普通文件和文件夹,这意味着可以检查PV源存储,查看仓库是否向其写入了文件。
在生产环境中,这是通过访问外部NFS服务器来完成的。但是,在本环境中,NFS共享是在services的VM上配置的,因此ssh至services查看,以便于验证OCP内部仓库成功将image存储到持久存储中。
示例:一个名为hello的应用程序在default命名空间中运行,下面的命令验证图像是否存储在持久存储中。
  1 [user@demo ~] ssh root@master ls -l \
2 /var/export/registryvol/docker/registry/v2/repositories/default/

四 PV综合实验

4.1 前置准备

准备完整的OpenShift集群,参考《003.OpenShift网络》2.1。

4.2 本练习准备

[student@workstation ~]$ lab storage-review setup

4.3 配置NFS

本实验不详解NFS的配置和创建,直接使用/root/DO280/labs/deploy-volume/config-nfs.sh脚本实现,具体脚本内容可通过以下方式查看。
同时NFS由services节点提供。
  1 [root@services ~]# less -FiX /root/DO280/labs/storage-review/config-review-nfs.sh
2 [root@services ~]# /root/DO280/labs/storage-review/config-review-nfs.sh #创建NFS
3 [root@services ~]# showmount -e #确认验证

4.4 创建持久卷

  1 [student@workstation ~]$ oc login -u admin -p redhat https://master.lab.example.com
2 [student@workstation ~]$ less -FiX /home/student/DO280/labs/storage-review/review-volume-pv.yaml
3 apiVersion: v1
4 kind: PersistentVolume
5 metadata:
6 name: review-pv
7 spec:
8 capacity:
9 storage: 3Gi
10 accessModes:
11 - ReadWriteMany
12 nfs:
13 path: /var/export/review-dbvol
14 server: services.lab.example.com
15 persistentVolumeReclaimPolicy: Recycle
16 [student@workstation ~]$ oc create -f /home/student/DO280/labs/storage-review/review-volume-pv.yaml
17 [student@workstation ~]$ oc get pv #查看PV
18 NAME CAPACITYACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
19 review-pv 3Gi RWX Recycle Available 13s
4.5 部署模板
  1 [student@workstation ~]$ less -FiX /home/student/DO280/labs/storage-review/instructor-template.yaml
2 [student@workstation ~]$ oc create -n openshift -f /home/student/DO280/labs/storage-review/instructor-template.yaml
3 #使用模板创建应用至openshift namespace中

4.6 创建项目

  1 [student@workstation ~]$ oc login -u developer -p redhat https://master.lab.example.com
2 [student@workstation ~]$ oc new-project instructor

4.7 web部署应用

浏览器访问: https://master.lab.example.com
选择Catalog
选择PHP,并使用instructor-template。
设置Application Hostname,然后直接下一步,模板会创建一个数据库服务器。
单击Continue to project overview以监视应用程序的构建过程。从提供的服务框架中,单击讲师。单击部署配置#1条目旁边的下拉箭头,打开部署面板。当构建完成时,build部分的Complete旁边应该出现一个绿色的复选标记。
4.9 端口转发
  1 [student@workstation ~]$ oc login -u developer -p redhat https://master.lab.example.com
2 [student@workstation ~]$ oc get pod
3 NAME READY STATUS RESTARTS AGE
4 instructor-1-9fmct 1/1 Running 0 43s
5 instructor-1-build 0/1 Completed 0 2m
6 mysql-1-f7rrq 1/1 Running 0 2m
7 [student@workstation ~]$ oc port-forward mysql-1-f7rrq 3306:3306

4.10 填充数据库

  1 [student@workstation ~]$ mysql -h127.0.0.1 -u instructor -ppassword \
2 instructor < /home/student/DO280/labs/storage-review/instructor.sql
3 [student@workstation ~]$ mysql -h127.0.0.1 -u instructor -ppassword instructor -e "select * from instructors;" #查看
4

4.11 测试访问

workstations的浏览器访问:http://instructor.apps.lab.example.com

4.11 测试添加数据



4.13 确认验证

  1 [student@workstation ~]$ lab storage-review grade		#环境脚本判断实验

4.14 清理删除

  1 [student@workstation ~]$ oc login -uadmin -predhat
2 [student@workstation ~]$ oc delete project instructor
3 [student@workstation ~]$ oc delete pv review-pv
4 [student@workstation ~]$ ssh root@services
5 [root@services ~]# rm -rf /var/export/review-dbvol
6 [root@services ~]# rm -f /etc/exports.d/review-dbvol.exports

最新文章

  1. Android性能优化之UncaughtExceptionHandler定制自己的错误日志系统
  2. 浩瀚先森(guohao1206.com)
  3. https://google-developers.appspot.com/chart/
  4. 第三十一篇、iOS 9版本适配
  5. windows下Qt5.1 for android开发环境配置(PS:Qt5.2出来了哈,稳定)
  6. CSS 组合选择符
  7. .NET 4.0 兼容 .NET 2.0 的方法
  8. NET基础课--JIT编译器如何工作1
  9. javascript模仿块级作用域(第一篇)
  10. Qt 外观之一 ——Qt Style Sheet
  11. 数据契约(DataContract)的作用
  12. php基本数据类型需要注意的地方
  13. (转)Amoeba for MySQL 非常好用的mysql集群软件
  14. JavaScript中对事件简单的理解(2)
  15. 翻译:MariaDB字符集和排序规则
  16. 浅析is和as两个关键词在类型转换时的使用
  17. pta-树种统计
  18. Locust:简介和基本用法
  19. vue.js的计算机属性学习
  20. Session和Cookie,以及用户登录验证问题。

热门文章

  1. Spring基础之AOP
  2. Set接口中的HashSet,LinkedHashSet,TreeSet
  3. 【Gradle教程】Gradle 入门
  4. STM32读取HX711(AD)模块数据——压力传感器
  5. 关于thisState的那些事
  6. 关于STL-map容器
  7. jQuery-操作元素的内容,属性,样式
  8. PowerPC-MPC56xx Flash模式启动过程
  9. 数据库之 MySQL --- 数据处理 之 表的约束与分页(七)
  10. Java实现 蓝桥杯VIP 算法训练 P1101