前端技术
HTML
CSS
Javascript
前端框架和UI库
VUE
ReactJS
AngularJS
JQuery
NodeJS
JSON
Element-UI
Bootstrap
Material UI
服务端和客户端
Java
Python
PHP
Golang
Scala
Kotlin
Groovy
Ruby
Lua
.net
c#
c++
后端WEB和工程框架
SpringBoot
SpringCloud
Struts2
MyBatis
Hibernate
Tornado
Beego
Go-Spring
Go Gin
Go Iris
Dubbo
HessianRPC
Maven
Gradle
数据库
MySQL
Oracle
Mongo
中间件与web容器
Redis
MemCache
Etcd
Cassandra
Kafka
RabbitMQ
RocketMQ
ActiveMQ
Nacos
Consul
Tomcat
Nginx
Netty
大数据技术
Hive
Impala
ClickHouse
DorisDB
Greenplum
PostgreSQL
HBase
Kylin
Hadoop
Apache Pig
ZooKeeper
SeaTunnel
Sqoop
Datax
Flink
Spark
Mahout
数据搜索与日志
ElasticSearch
Apache Lucene
Apache Solr
Kibana
Logstash
数据可视化与OLAP
Apache Atlas
Superset
Saiku
Tesseract
系统与容器
Linux
Shell
Docker
Kubernetes
[访问修饰符 ]的搜索结果
这里是文章列表。热门标签的颜色随机变换,标签颜色没有特殊含义。
点击某个标签可搜索标签相关的文章。
点击某个标签可搜索标签相关的文章。
转载文章
...发者开始关注网站的可访问性、SEO优化以及用户体验。 近日,W3C组织发布了最新的HTML5.2规范,其中对现有元素的功能定义进行了细化和完善,同时新增了部分元素以适应不断变化的网页内容需求。例如,元素用于创建对话框或模态窗口,为用户提供更加直观的交互体验; 和 组合使用,让图片和图表等媒介内容具有更清晰的上下文描述。 此外,各大浏览器厂商也在持续更新对HTML5特性的支持。Chrome、Firefox和Safari等主流浏览器已全面支持这些语义元素,并针对无障碍浏览(Accessibility)进行了一系列改进。例如,通过ARIA属性配合HTML5语义元素,可以更好地帮助屏幕阅读器等辅助技术理解和解析页面结构。 对于实际应用层面,许多前端框架如Bootstrap、Foundation等也积极采纳HTML5语义化元素,使得开发者能够更容易地构建具有良好语义结构和响应式布局的网页。同时,随着Google等搜索引擎对网站质量评价标准的不断提升,遵循语义化标签规范的网页将更有可能获得更好的搜索排名。 总之,紧跟HTML5的发展步伐并将其语义化特性融入到日常开发实践中,不仅能提升网页的可读性和易用性,也有利于实现搜索引擎优化,从而推动网站流量的增长与用户满意度的提升。未来,随着Web Components、Shadow DOM等新特性的普及,HTML5将在构建模块化、组件化的现代Web应用程序中发挥更加关键的作用。
2023-07-16 11:42:34
252
转载
转载文章
...tr就会失效,再用其访问裸指针也会发生和auto_ptr相似的crash,如下面示例代码,所以,即使使用了unique_ptr,也要慎重使用move方法,防止指针所有权被转移。 unique_ptr<int> up(new int(5));//auto up2 = up; // 编译错误auto up2 = move(up);cout << up << endl; //crash,up已经失效,无法访问其裸指针 除了上述用法,unique_ptr还支持创建动态数组。在C++中,创建数组有很多方法,如下所示: // 静态数组,在编译时决定了数组大小int arr[10];// 通过指针创建在堆上的数组,可在运行时动态指定数组大小,但需要手动释放内存int arr = new int[10];// 通过std::vector容器创建动态数组,无需手动释放数组内存vector<int> arr(10);// 通过unique_ptr创建动态数组,也无需手动释放数组内存,比vector更轻量化unique_ptr<int[]> arr(new int[10]); 这里需要注意的是,不管vector还是unique_ptr,虽然可以帮我们自动释放数组内存,但如果数组的元素是复杂数据类型时,我们还需要在其析构函数中正确释放内存。 真正的智能指针:shared_ptr auto_ptr和unique_ptr都有或多或少的缺陷,因此C++11还推出了shared_ptr,这也是目前工程内使用最多最广泛的智能指针,他使用引用计数(感觉有参考Objective-C的嫌疑),实现对同一块内存可以有多个引用,在最后一个引用被释放时,指向的内存才释放,这也是和unique_ptr最大的区别。 另外,使用shared_ptr过程中有几点需要注意: 构造shared_ptr的方法,如下示例代码所示,我们尽量使用shared_ptr构造函数或者make_shared的方式创建shared_ptr,禁止使用裸指针赋值的方式,这样会shared_ptr难于管理指针的生命周期。 // 使用裸指针赋值构造,不推荐,裸指针被释放后,shared_ptr就野了,不能完全控制裸指针的生命周期,失去了智能指针价值int p = new int(10);shared_ptr<int>sp = p;delete p; // sp将成为野指针,使用sp将crash// 将裸指针作为匿名指针传入构造函数,一般做法,让shared_ptr接管裸指针的生命周期,更安全shared_ptr<int>sp1(new int(10));// 使用make_shared,推荐做法,更符合工厂模式,可以连代码中的所有new,更高效;方法的参数是用来初始化模板类shared_ptr<int>sp2 = make_shared<int>(10); 禁止使用指向shared_ptr的裸指针,也就是智能指针的指针,这听起来就很奇怪,但开发中我们还需要注意,使用shared_ptr的指针指向一个shared_ptr时,引用计数并不会加一,操作shared_ptr的指针很容易就发生野指针异常。 shared_ptr<int>sp = make_shared<int>(10);cout << sp.use_count() << endl; //输出1shared_ptr<int> sp1 = &sp;cout << (sp1).use_count() << endl; //输出依然是1(sp1).reset(); //sp成为野指针cout << sp << endl; //crash 使用shared_ptr创建动态数组,在介绍unique_ptr时我们就讲过创建动态数组,而shared_ptr同样可以做到,不过稍微复杂一点,如下代码所示,除了要显示指定析构方法外(因为默认是T的析构函数,不是T[]),另外对外的数据类型依然是shared_ptr<T>,非常有迷惑性,看不出来是数组,最后不能直接使用下标读写数组,要先get()获取裸指针才可以使用下标。所以,不推荐使用shared_ptr来创建动态数组,尽量使用unique_ptr,这可是unique_ptr为数不多的优势了。 template <typename T>shared_ptr<T> make_shared_array(size_t size) {return shared_ptr<T>(new T[size], default_delete<T[]>());}shared_ptr<int>sp = make_shared_array(10); //看上去是shared<int>类型,实际上是数组sp.get()[0] = 100; //不能直接使用下标读写数组元素,需要通过get()方法获取裸指针后再操作 用shared_ptr实现多态,在我们使用裸指针时,实现多态就免不了定义虚函数,那么用shared_ptr时也不例外,不过有一处是可以省下的,就是析构函数我们不需要定义为虚函数了,如下面代码所示: class A {public:~A() {cout << "dealloc A" << endl;} };class B : public A {public:~B() {cout << "dealloc B" << endl;} };int main(int argc, const char argv[]) {A a = new B();delete a; //只打印dealloc Ashared_ptr<A>spa = make_shared<B>(); //析构spa是会先打印dealloc B,再打印dealloc Areturn 0;} 循环引用,笔者最先接触引用计数的语言就是Objective-C,而OC中最常出现的内存问题就是循环引用,如下面代码所示,A中引用B,B中引用A,spa和spb的强引用计数永远大于等于1,所以直到程序退出前都不会被退出,这种情况有时候在正常的业务逻辑中是不可避免的,而解决循环引用的方法最有效就是改用weak_ptr,具体可见下一章。 class A {public:shared_ptr<B> b;};class B {public:shared_ptr<A> a;};int main(int argc, const char argv[]) {shared_ptr<A> spa = make_shared<A>();shared_ptr<B> spb = make_shared<B>();spa->b = spb;spb->a = spa;return 0;} //main函数退出后,spa和spb强引用计数依然为1,无法释放 刚柔并济:weak_ptr 正如上一章提到,使用shared_ptr过程中有可能会出现循环引用,关键原因是使用shared_ptr引用一个指针时会导致强引用计数+1,从此该指针的生命周期就会取决于该shared_ptr的生命周期,然而,有些情况我们一个类A里面只是想引用一下另外一个类B的对象,类B对象的创建不在类A,因此类A也无需管理类B对象的释放,这个时候weak_ptr就应运而生了,使用shared_ptr赋值给一个weak_ptr不会增加强引用计数(strong_count),取而代之的是增加一个弱引用计数(weak_count),而弱引用计数不会影响到指针的生命周期,这就解开了循环引用,上一章最后的代码使用weak_ptr可改造为如下代码。 class A {public:shared_ptr<B> b;};class B {public:weak_ptr<A> a;};int main(int argc, const char argv[]) {shared_ptr<A> spa = make_shared<A>();shared_ptr<B> spb = make_shared<B>();spa->b = spb; //spb强引用计数为2,弱引用计数为1spb->a = spa; //spa强引用计数为1,弱引用计数为2return 0;} //main函数退出后,spa先释放,spb再释放,循环解开了使用weak_ptr也有需要注意的点,因为既然weak_ptr不负责裸指针的生命周期,那么weak_ptr也无法直接操作裸指针,我们需要先转化为shared_ptr,这就和OC的Strong-Weak Dance有点像了,具体操作如下:shared_ptr<int> spa = make_shared<int>(10);weak_ptr<int> spb = spa; //weak_ptr无法直接使用裸指针创建if (!spb.expired()) { //weak_ptr最好判断是否过期,使用expired或use_count方法,前者更快spb.lock() += 10; //调用weak_ptr转化为shared_ptr后再操作裸指针}cout << spa << endl; //20 智能指针原理 看到这里,智能指针的用法基本介绍完了,后面笔者来粗浅地分析一下为什么智能指针可以有效帮我们管理裸指针的生命周期。 使用栈对象管理堆对象 在C++中,内存会分为三部分,堆、栈和静态存储区,静态存储区会存放全局变量和静态变量,在程序加载时就初始化,而堆是由程序员自行分配,自行释放的,例如我们使用裸指针分配的内存;而最后栈是系统帮我们分配的,所以也会帮我们自动回收。因此,智能指针就是利用这一性质,通过一个栈上的对象(shared_ptr或unique_ptr)来管理一个堆上的对象(裸指针),在shared_ptr或unique_ptr的析构函数中判断当前裸指针的引用计数情况来决定是否释放裸指针。 shared_ptr引用计数的原理 一开始笔者以为引用计数是放在shared_ptr这个模板类中,但是细想了一下,如果这样将shared_ptr赋值给另一个shared_ptr时,是怎么做到两个shared_ptr的引用计数同时加1呢,让等号两边的shared_ptr中的引用计数同时加1?不对,如果还有第二个shared_ptr再赋值给第三个shared_ptr那怎么办呢?或许通过下面的类图便清楚个中奥秘。 [ boost中shared_ptr与weak_ptr类图 ] 我们重点关注shared_ptr<T>的类图,它就是我们可以直接操作的类,这里面包含裸指针T,还有一个shared_count的对象,而shared_count对象还不是最终的引用计数,它只是包含了一个指向sp_counted_base的指针,这应该就是真正存放引用计数的地方,包括强应用计数和弱引用计数,而且shared_count中包含的是sp_counted_base的指针,不是对象,这也就意味着假如shared_ptr<T> a = b,那么a和b底层pi_指针指向的是同一个sp_counted_base对象,这就很容易做到多个shared_ptr的引用计数永远保持一致了。 多线程安全 本章所说的线程安全有两种情况: 多个线程操作多个不同的shared_ptr对象 C++11中声明了shared_ptr的计数操作具有原子性,不管是赋值导致计数增加还是释放导致计数减少,都是原子性的,这个可以参考sp_counted_base的源码,因此,基于这个特性,假如有多个shared_ptr共同管理一个裸指针,那么多个线程分别通过不同的shared_ptr进行操作是线程安全的。 多个线程操作同一个shared_ptr对象 同样的道理,既然C++11只负责sp_counted_base的原子性,那么shared_ptr本身就没有保证线程安全了,加入两个线程同时访问同一个shared_ptr对象,一个进行释放(reset),另一个读取裸指针的值,那么最后的结果就不确定了,很有可能发生野指针访问crash。 作者:腾讯技术工程 https://mp.weixin.qq.com/s?__biz=MjM5ODYwMjI2MA==&mid=2649743462&idx=1&sn=c9d94ddc25449c6a0052dc48392a33c2&utm_source=tuicool&utm_medium=referralmp.weixin.qq.com 本篇文章为转载内容。原文链接:https://blog.csdn.net/weixin_31467557/article/details/113049179。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-02-24 18:25:46
141
转载
转载文章
...的机器)的文件系统上访问到。统一文件系统(union file system)技术能够将不同的层整合成一个文件系统,为这些层提供了一个统一的视角,这样就隐藏了多层的存在,在用户的角度看来,只存在一个文件系统。 我们可以在图片的右边看到这个视角的形式。 你可以在你的主机文件系统上找到有关这些层的文件。需要注意的是,在一个运行中的容器内部,这些层是不可见的。在我的主机上,我发现它们存在于/var/lib/docker/aufs目录下。 sudo tree -L 1 /var/lib/docker/ /var/lib/docker/├── aufs├── containers├── graph├── init├── linkgraph.db├── repositories-aufs├── tmp├── trust└── volumes7 directories, 2 files Container Definition 容器(container)的定义和镜像(image)几乎一模一样,也是一堆层的统一视角,唯一区别在于容器的最上面那一层是可读可写的。 细心的读者可能会发现,容器的定义并没有提及容器是否在运行,没错,这是故意的。正是这个发现帮助我理解了很多困惑。 要点:容器 = 镜像 + 可读层。并且容器的定义并没有提及是否要运行容器。 接下来,我们将会讨论运行态容器。 Running Container Definition 一个运行态容器(running container)被定义为一个可读写的统一文件系统加上隔离的进程空间和包含其中的进程。下面这张图片展示了一个运行中的容器。 正是文件系统隔离技术使得Docker成为了一个前途无量的技术。一个容器中的进程可能会对文件进行修改、删除、创建,这些改变都将作用于可读写层(read-write layer)。下面这张图展示了这个行为。 我们可以通过运行以下命令来验证我们上面所说的: docker run ubuntu touch happiness.txt 即便是这个ubuntu容器不再运行,我们依旧能够在主机的文件系统上找到这个新文件。 find / -name happiness.txt /var/lib/docker/aufs/diff/860a7b...889/happiness.txt Image Layer Definition 为了将零星的数据整合起来,我们提出了镜像层(image layer)这个概念。下面的这张图描述了一个镜像层,通过图片我们能够发现一个层并不仅仅包含文件系统的改变,它还能包含了其他重要信息。 元数据(metadata)就是关于这个层的额外信息,它不仅能够让Docker获取运行和构建时的信息,还包括父层的层次信息。需要注意,只读层和读写层都包含元数据。 除此之外,每一层都包括了一个指向父层的指针。如果一个层没有这个指针,说明它处于最底层。 Metadata Location: 我发现在我自己的主机上,镜像层(image layer)的元数据被保存在名为”json”的文件中,比如说: /var/lib/docker/graph/e809f156dc985.../json e809f156dc985...就是这层的id 一个容器的元数据好像是被分成了很多文件,但或多或少能够在/var/lib/docker/containers/<id>目录下找到,<id>就是一个可读层的id。这个目录下的文件大多是运行时的数据,比如说网络,日志等等。 全局理解(Tying It All Together) 现在,让我们结合上面提到的实现细节来理解Docker的命令。 docker create <image-id> docker create 命令为指定的镜像(image)添加了一个可读写层,构成了一个新的容器。注意,这个容器并没有运行。 docker start <container-id> Docker start命令为容器文件系统创建了一个进程隔离空间。注意,每一个容器只能够有一个进程隔离空间。 docker run <image-id> 看到这个命令,读者通常会有一个疑问:docker start 和 docker run命令有什么区别。 从图片可以看出,docker run 命令先是利用镜像创建了一个容器,然后运行这个容器。这个命令非常的方便,并且隐藏了两个命令的细节,但从另一方面来看,这容易让用户产生误解。 题外话:继续我们之前有关于Git的话题,我认为docker run命令类似于git pull命令。git pull命令就是git fetch 和 git merge两个命令的组合,同样的,docker run就是docker create和docker start两个命令的组合。 docker ps docker ps 命令会列出所有运行中的容器。这隐藏了非运行态容器的存在,如果想要找出这些容器,我们需要使用下面这个命令。 docker ps –a docker ps –a命令会列出所有的容器,不管是运行的,还是停止的。 docker images docker images命令会列出了所有顶层(top-level)镜像。实际上,在这里我们没有办法区分一个镜像和一个只读层,所以我们提出了top-level 镜像。只有创建容器时使用的镜像或者是直接pull下来的镜像能被称为顶层(top-level)镜像,并且每一个顶层镜像下面都隐藏了多个镜像层。 docker images –a docker images –a命令列出了所有的镜像,也可以说是列出了所有的可读层。如果你想要查看某一个image-id下的所有层,可以使用docker history来查看。 docker stop <container-id> docker stop命令会向运行中的容器发送一个SIGTERM的信号,然后停止所有的进程。 docker kill <container-id> docker kill 命令向所有运行在容器中的进程发送了一个不友好的SIGKILL信号。 docker pause <container-id> docker stop和docker kill命令会发送UNIX的信号给运行中的进程,docker pause命令则不一样,它利用了cgroups的特性将运行中的进程空间暂停。具体的内部原理你可以在这里找到:https://www.kernel.org/doc/Doc ... m.txt,但是这种方式的不足之处在于发送一个SIGTSTP信号对于进程来说不够简单易懂,以至于不能够让所有进程暂停。 docker rm <container-id> docker rm命令会移除构成容器的可读写层。注意,这个命令只能对非运行态容器执行。 docker rmi <image-id> docker rmi 命令会移除构成镜像的一个只读层。你只能够使用docker rmi来移除最顶层(top level layer)(也可以说是镜像),你也可以使用-f参数来强制删除中间的只读层。 docker commit <container-id> docker commit命令将容器的可读写层转换为一个只读层,这样就把一个容器转换成了不可变的镜像。 docker build docker build命令非常有趣,它会反复的执行多个命令。 我们从上图可以看到,build命令根据Dockerfile文件中的FROM指令获取到镜像,然后重复地1)run(create和start)、2)修改、3)commit。在循环中的每一步都会生成一个新的层,因此许多新的层会被创建。 docker exec <running-container-id> docker exec 命令会在运行中的容器执行一个新进程。 docker inspect <container-id> or <image-id> docker inspect命令会提取出容器或者镜像最顶层的元数据。 docker save <image-id> docker save命令会创建一个镜像的压缩文件,这个文件能够在另外一个主机的Docker上使用。和export命令不同,这个命令为每一个层都保存了它们的元数据。这个命令只能对镜像生效。 docker export <container-id> docker export命令创建一个tar文件,并且移除了元数据和不必要的层,将多个层整合成了一个层,只保存了当前统一视角看到的内容(译者注:expoxt后 的容器再import到Docker中,通过docker images –tree命令只能看到一个镜像;而save后的镜像则不同,它能够看到这个镜像的历史镜像)。 docker history <image-id> docker history命令递归地输出指定镜像的历史镜像。 参考: http://www.cnblogs.com/bethal/p/5942369.html 本篇文章为转载内容。原文链接:https://blog.csdn.net/u010098331/article/details/53485539。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-11-26 15:47:20
538
转载
Docker
...能像穿越空间一样,去访问别的机器上跑着的那些“小房子”(Docker实例)。这种功能对于运维人员来说简直是福音! --- 4. Rancher 企业级的容器编排利器 如果你是一个团队协作的开发者,或者正在运营一个大规模的服务集群,那么Rancher可能是你的最佳选择。它不仅仅是一个Docker管理工具,更是一个完整的容器编排平台。 Rancher的核心优势在于它的“多集群管理”能力。想象一下,你的公司有好几台服务器,分别放在地球上的不同角落,有的在美国,有的在欧洲,还有的在中国。每台服务器上都跑着各种各样的服务,比如网站、数据库啥的。这时候,Rancher就派上用场了!它就像一个超级贴心的小管家,让你不用到处切换界面,在一个地方就能轻松搞定所有服务器和服务的管理工作,省时又省力! 举个例子,如果你想在Rancher中添加一个新的节点,只需要几步操作即可完成: 1. 登录Rancher控制台。 2. 点击“Add Cluster”按钮。 3. 输入目标节点的信息(IP地址、SSH密钥等)。 4. 等待几分钟,Rancher会自动为你安装必要的组件。 一旦节点加入成功,你就可以直接在这个界面上部署应用了。比如,用Kubernetes部署一个Redis集群: bash kubectl create deployment redis --image=redis:alpine kubectl expose deployment redis --type=LoadBalancer --port=6379 虽然这条命令看起来很简单,但它背后实际上涉及到了复杂的调度逻辑和网络配置。而Rancher把这些复杂的事情封装得很好,让我们可以专注于业务本身。 --- 5. Traefik 反向代理与负载均衡的最佳拍档 最后要介绍的是Traefik,这是一个轻量级的反向代理工具,专门用来处理HTTP请求的转发和负载均衡。它最厉害的地方啊,就是能跟Docker完美地融为一体,还能根据容器上的标签,自动调整路由规则呢! 比如说,你有两个服务分别监听在8080和8081端口,现在想通过一个域名访问它们。只需要给这两个容器加上相应的标签: yaml labels: - "traefik.enable=true" - "traefik.http.routers.service1.rule=Host(service1.example.com)" - "traefik.http.services.service1.loadbalancer.server.port=8080" - "traefik.http.routers.service2.rule=Host(service2.example.com)" - "traefik.http.services.service2.loadbalancer.server.port=8081" 这样一来,当用户访问service1.example.com时,Traefik会自动将请求转发到监听8080端口的容器;而访问service2.example.com则会指向8081端口。这种方式不仅高效,还极大地减少了配置的工作量。 --- 6. 总结 找到最适合自己的工具 好了,到这里咱们已经聊了不少关于服务器管理工具的话题。从Docker到Portainer,再到Rancher和Traefik,每一种工具都有其独特的优势和适用场景。 我的建议是,先根据自己的需求确定重点。要是你只想弄个小玩意儿,图个省事儿快点搞起来,那用Docker配个Portainer就完全够用了。但要是你们团队一起干活儿,或者要做大范围的部署,那Rancher这种专业的“老司机工具”就得安排上啦! 当然啦,技术的世界永远没有绝对的答案。其实啊,很多时候你会发现,最适合你的工具不一定是最火的那个,而是那个最合你心意、用起来最顺手的。就像穿鞋一样,别人觉得好看的根本不合脚,而那双不起眼的小众款却让你走得又稳又舒服!所以啊,在用这些工具的时候,别光顾着看,得多动手试试,边用边记下自己的感受和想法,这样你才能真的搞懂它们到底有啥门道! 好了,今天的分享就到这里啦!如果你还有什么问题或者想法,欢迎随时留言交流哦~咱们下次再见啦!
2025-04-16 16:05:13
97
月影清风_
Hadoop
...doop支持文件的跨访问控制协议迁移 一、初识Hadoop 为什么它如此重要? 嗨,朋友们!如果你对大数据处理感兴趣,那你一定听说过Hadoop这个名字。嘿,作为一个码农,我跟Hadoop的初次见面真的把我惊呆了!它的功能太牛了,感觉就像发现了一个全新的世界,简直太酷了吧!简单说呢,Hadoop就是一个开源的“大数据管家”,专门负责存东西、弄数据,而且不管数据多到啥程度,它都能应付得漂漂亮亮的!它就像是一个超级仓库,可以轻松应对各种规模的数据任务。 为什么Hadoop这么受欢迎呢?因为它解决了传统数据库在处理大规模数据时的瓶颈问题。比如说啊,你在一家电商公司当数据分析师,每天的工作就是跟上亿条用户的点击、浏览、下单这些行为记录打交道,简直就像在海量的信息海洋里淘宝一样!如果用传统的数据库,可能早就崩溃了。但Hadoop不一样,它可以将这些数据分散到多个服务器上进行并行处理,效率杠杠的! 不过,Hadoop的魅力远不止于此。嘿,大家好!今天我想跟你们分享一个关于Hadoop的超棒功能——它居然能让你在不同的访问控制协议之间轻松切换文件!是不是听着就很带感?哎呀,是不是觉得这事听着有点绕?别慌,我这就用大白话给你说道说道,保证你一听就明白! --- 二、什么是跨访问控制协议迁移? 首先,我们得明白什么是访问控制协议。简单说,就是规定谁可以访问你的数据以及他们能做些什么的规则。好比说啊,你有个公共文件柜,你想让一些人只能打开看看里面的东西,啥都不能动;但另外一些人呢,不仅能看,还能随便改,甚至直接把东西清空或者拿走。这就是访问控制协议的作用。 那么,“跨访问控制协议迁移”又是什么意思呢?想象一下,你有两个不同的系统,它们各自有自己的访问控制规则。比如说,一个是Linux那边的ACL(访问控制列表)系统,另一个则是Windows里的NTFS权限系统,两者各有各的玩法。现在,你要把文件从一个系统迁移到另一个系统,而且你还想保留原来的访问控制设置。这就需要用到跨访问控制协议迁移的技术了。 为什么要关心这个功能呢?因为现实世界中,企业往往会有多种操作系统和存储环境。要是你对文件的权限管理不当,那可就麻烦了,要么重要数据被泄露出去,要么一不小心就把东西给搞砸了。而Hadoop通过其强大的灵活性,完美地解决了这个问题。 --- 三、Hadoop如何实现跨访问控制协议迁移? 接下来,让我们来看看Hadoop是如何做到这一点的。其实,这主要依赖于Hadoop的分布式文件系统(HDFS)和它的API库。为了更好地理解,我们可以一步步来分析。 3.1 HDFS的基本概念 HDFS是Hadoop的核心组件之一,它是用来存储大量数据的分布式文件系统。这就像是一个超大号的硬盘,不过它有点特别,不是集中在一个地方存东西,而是把数据切成小块,分散到不同的“小房间”里去。这样做的好处是即使某个节点坏了,也不会影响整个系统的运行。 HDFS还提供了一套丰富的接口,允许开发者自定义文件的操作行为。这就为实现跨访问控制协议迁移提供了可能性。 3.2 实现步骤 实现跨访问控制协议迁移大致分为以下几个步骤: (1)读取源系统的访问控制信息 第一步是获取源系统的访问控制信息。比如,如果你正在从Linux系统迁移到Windows系统,你需要先读取Linux上的ACL配置。 java // 示例代码:读取Linux ACL import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import java.io.IOException; public class AccessControlReader { public static void main(String[] args) throws IOException { Path path = new Path("/path/to/source/file"); FileSystem fs = FileSystem.get(new Configuration()); // 获取ACL信息 String acl = fs.getAclStatus(path).toString(); System.out.println("Source ACL: " + acl); } } 这段代码展示了如何使用Hadoop API读取Linux系统的ACL信息。可以看到,Hadoop已经为我们封装好了相关的API,调用起来非常方便。 (2)转换为目标系统的格式 接下来,我们需要将读取到的访问控制信息转换为目标系统的格式。比如,将Linux的ACL转换为Windows的NTFS权限。 java // 示例代码:模拟ACL到NTFS的转换 public class AclToNtfsConverter { public static void convert(String linuxAcl) { // 这里可以编写具体的转换逻辑 System.out.println("Converting ACL to NTFS: " + linuxAcl); } } 虽然这里只是一个简单的打印函数,但实际上你可以根据实际需求编写复杂的转换算法。 (3)应用到目标系统 最后一步是将转换后的权限应用到目标系统上。这一步同样可以通过Hadoop提供的API来完成。 java // 示例代码:应用NTFS权限 public class NtfsPermissionApplier { public static void applyPermissions(Path targetPath, String ntfsPermissions) { try { // 模拟应用权限的过程 System.out.println("Applying NTFS permissions to " + targetPath.toString() + ": " + ntfsPermissions); } catch (Exception e) { e.printStackTrace(); } } } 通过这三个步骤,我们就完成了从源系统到目标系统的访问控制协议迁移。 --- 四、实战演练 一个完整的案例 为了让大家更直观地理解,我准备了一个完整的案例。好啦,想象一下,我们现在要干的事儿就是把一个文件从一台Linux服务器搬去Windows服务器,而且还得保证这个文件在新家里的“门禁权限”跟原来一模一样,不能搞错! 4.1 准备工作 首先,确保你的开发环境中已经安装了Hadoop,并且配置好相关的依赖库。此外,还需要准备两台机器,一台装有Linux系统,另一台装有Windows系统。 4.2 编写代码 接下来,我们编写代码来实现迁移过程。首先是读取Linux系统的ACL信息。 java // 读取Linux ACL Path sourcePath = new Path("/source/file.txt"); FileSystem linuxFs = FileSystem.get(new Configuration()); String linuxAcl = linuxFs.getAclStatus(sourcePath).toString(); System.out.println("Linux ACL: " + linuxAcl); 然后,我们将这些ACL信息转换为NTFS格式。 java // 模拟ACL到NTFS的转换 AclToNtfsConverter.convert(linuxAcl); 最后,将转换后的权限应用到Windows系统上。 java // 应用NTFS权限 Path targetPath = new Path("\\\\windows-server\\file.txt"); NtfsPermissionApplier.applyPermissions(targetPath, "Full Control"); 4.3 执行结果 执行完上述代码后,你会发现文件已经被成功迁移到了Windows系统,并且保留了原有的访问控制设置。是不是很神奇? --- 五、总结与展望 通过这篇文章,我相信你对Hadoop支持文件的跨访问控制协议迁移有了更深的理解。Hadoop不仅是一个强大的工具,更是一种思维方式的转变。它就像个聪明的老师,不仅教我们怎么用分布式的思路去搞定问题,还时不时敲打我们:嘿,别忘了数据的安全和规矩可不能丢啊! 未来,随着技术的发展,Hadoop的功能会越来越强大。我希望你能继续探索更多有趣的话题,一起在这个充满挑战的世界里不断前行! 加油吧,程序员们!
2025-04-29 15:54:59
79
风轻云淡
转载文章
...行过程中,该算法首先访问一个顶点,然后尽可能深地探索其邻接顶点,直到到达无法继续深入的顶点(即叶子节点或已访问过的节点),之后回溯至前一个顶点并尝试探索其他未访问的邻接顶点。在文中,深度优先遍历被列为了学习数据结构时需要掌握的基本算法之一,适用于多种与树和图相关的数据结构处理场景。
2023-09-12 23:35:52
134
转载
Redis
...到钥匙的服务实例才能访问共享资源。 命名空间 , 在分布式锁的实现中,为了区分不同业务逻辑所使用的锁,通常会在锁的名称前添加特定的前缀作为命名空间。这样可以避免不同业务逻辑之间的锁发生冲突或干扰,确保每个业务逻辑都有独立的锁机制。例如,订单系统的锁名称可能是orders:place_order,而积分系统的锁名称则是points:update_score。 Lua脚本 , 一种轻量级的脚本语言,Redis支持通过Lua脚本执行复杂的操作,确保操作的原子性。文章中提到,在解锁时使用Lua脚本来判断当前锁的所有者是否匹配,若匹配则删除该锁,否则拒绝操作。这种方式能够有效防止多线程环境下的竞态条件,保障解锁过程的安全性和一致性。
2025-04-22 16:00:29
58
寂静森林
转载文章
...发者通过编程方式动态访问和修改文档的内容、结构及样式。在文章所述的网络爬虫项目中,利用Jsoup进行DOM解析,以识别并提取目标网页上的图片链接及其他相关信息。
2023-06-12 10:26:04
130
转载
转载文章
...者提供了低级别的硬件访问权限,可以创建更复杂的3D图形和实时渲染效果,对于消除类游戏这类对响应速度有较高要求的游戏来说具有重大意义。 此外,游戏设计中的AI算法也是值得关注的方向。例如,运用深度学习和强化学习技术优化消除类游戏的智能提示系统,能有效提高玩家体验并延长游戏生命周期。一篇发表在“自然”杂志子刊上的论文就研究了AI在连连看等消除类游戏中的应用,展示了通过机器学习预测最佳消除路径的可能性。 总的来说,在继续深入实践HTML、CSS、JavaScript基础开发的同时,紧跟Web技术前沿进展,结合先进的编程语言、图形处理技术和AI算法,将有助于开发者打造出更为丰富、流畅且富有挑战性的消除类游戏产品,不断满足日益增长的用户体验需求。
2023-06-08 15:26:34
516
转载
转载文章
...》一文强调了实施严格访问控制、审计跟踪、加密传输和透明数据加密等功能的重要性,并引用了最新的行业标准和法规要求作为依据。 对于开发者而言,学习并掌握MySQL的高级特性以及最佳实践至关重要。近日,Oracle发布了MySQL HeatWave,这是一种融合分析型数据库引擎,能在同一个MySQL数据库中实现事务处理与实时分析,极大简化了大数据处理流程,提升了业务决策速度。 综上所述,了解MySQL的最新动态和技术演进不仅可以帮助我们更好地进行日常的数据库管理工作,还能洞悉未来数据库技术的发展趋势,从而为我们的系统设计与优化提供有力支撑。在实战中,结合具体业务场景灵活运用SQL语句及数据库管理系统,将有效提升整个系统的稳定性和效率。
2024-02-16 12:44:07
544
转载
转载文章
...的${key}属性被访问了)return value},set: function (newValue) {console.log(监听到obj对象的${key}属性被设置值)value = newValue} })})obj.name = 'kobe'obj.age = 30console.log(obj.name)console.log(obj.age)/ 监听到obj对象的name属性被设置值监听到obj对象的age属性被设置值监听到obj对象的name属性被访问了kobe监听到obj对象的age属性被访问了30/ 属性描述符监听对象的缺点: 首先,Object.defineProperty 设计的初衷,不是为了去监听截止一个对象中所有的属性的 我们在定义某些属性的时候,初衷其实是定义普通的属性,但是后面我们强行将它变成了数据属性描述符 其次,如果我们想监听更加丰富的操作,比如新增属性、删除属性,那么 Object.defineProperty 是无能为力的 所以我们要知道,存储数据描述符设计的初衷并不是为了去监听一个完整的对象 Ps: 原来的对象是 数据属性描述符,通过 Object.defineProperty 变成了 访问属性描述符 2. Proxy基本使用 在ES6中,新增了一个Proxy类,这个类从名字就可以看出来,是用于帮助我们创建一个代理的: 也就是说,如果我们希望监听一个对象的相关操作,那么我们可以先创建一个代理对象(Proxy对象) 之后对该对象的所有操作,都通过代理对象来完成,代理对象可以监听我们想要对原对象进行哪些操作 将上面的案例用 Proxy 来实现一次: 首先,我们需要 new Proxy 对象,并且传入需要侦听的对象以及一个处理对象,可以称之为 handler; const p = new Proxy(target, handler) 其次,我们之后的操作都是直接对 Proxy 的操作,而不是原有的对象,因为我们需要在 handler 里面进行侦听 const obj = {name: 'why',age: 18}const objProxy = new Proxy(obj, {// 获取值时的捕获器get: function (target, key) {console.log(监听到obj对象的${key}属性被访问了)return target[key]},// 设置值时的捕获器set: function (target, key, newValue) {console.log(监听到obj对象的${key}属性被设置值)target[key] = newValue} })console.log(objProxy.name)console.log(objProxy.age)objProxy.name = 'kobe'objProxy.age = 30console.log(obj.name)console.log(obj.age)/ 监听到obj对象的name属性被访问了why监听到obj对象的age属性被访问了18监听到obj对象的name属性被设置值监听到obj对象的age属性被设置值kobe30/ 2.1 Proxy 的 set 和 get 捕获器 如果我们想要侦听某些具体的操作,那么就可以在 handler 中添加对应的捕捉器(Trap) set 和 get 分别对应的是函数类型 set 函数有四个参数: target:目标对象(侦听的对象) property:将被设置的属性 key value:新属性值 receiver:调用的代理对象 get 函数有三个参数 target:目标对象(侦听的对象) property:被获取的属性 key receiver:调用的代理对象 2.2 Proxy 所有捕获器 (13个) handler.getPrototypeOf() Object.getPrototypeOf 方法的捕捉器 handler.setPrototypeOf() Object.setPrototypeOf 方法的捕捉器 handler.isExtensible() Object.isExtensible 方法的捕捉器 handler.preventExtensions() Object.preventExtensions 方法的捕捉器 handler.getOwnPropertyDescriptor() Object.getOwnPropertyDescriptor 方法的捕捉器 handler.defineProperty() Object.defineProperty 方法的捕捉器 handler.ownKeys() Object.getOwnPropertyNames 方法和 Object.getOwnPropertySymbols 方法的捕捉器 handler.has() in 操作符的捕捉器 handler.get() 属性读取操作的捕捉器 handler.set() 属性设置操作的捕捉器 handler.deleteProperty() delete 操作符的捕捉器 handler.apply() 函数调用操作的捕捉器 handler.construct() new 操作符的捕捉器 const obj = {name: 'why',age: 18}const objProxy = new Proxy(obj, {// 获取值时的捕获器get: function (target, key) {console.log(监听到obj对象的${key}属性被访问了)return target[key]},// 设置值时的捕获器set: function (target, key, newValue) {console.log(监听到obj对象的${key}属性被设置值)target[key] = newValue},// 监听 in 的捕获器has: function (target, key) {console.log(监听到obj对象的${key}属性的in操作)return key in target},// 监听 delete 的捕获器deleteProperty: function (target, key) {console.log(监听到obj对象的${key}属性的delete操作)delete target[key]} })// in 操作符console.log('name' in objProxy)// delete 操作delete objProxy.name/ 监听到obj对象的name属性的in操作true监听到obj对象的name属性的delete操作/ 2.3 Proxy 的 construct 和 apply 到捕捉器中还有 construct 和 apply,它们是应用于函数对象的 function foo() {console.log('调用了 foo')}const fooProxy = new Proxy(foo, {apply: function (target, thisArg, argArray) {console.log(对 foo 函数进行了 apply 调用)target.apply(thisArg, argArray)},construct: function (target, argArray, newTarget) {console.log(对 foo 函数进行了 new 调用)return new target(...argArray)} })fooProxy.apply({}, ['abc', 'cba'])new fooProxy('abc', 'cba')/ 对 foo 函数进行了 apply 调用调用了 foo对 foo 函数进行了 new 调用调用了 foo/ 3. Reflect 3.1 Reflect 的作用 Reflect 也是 ES6 新增的一个 API,它是一个对象,字面的意思是反射 Reflect 的作用: 它主要提供了很多操作 JavaScript 对象的方法,有点像 Object 中操作对象的方法 比如 Reflect.getPrototypeOf(target) 类似于 Object.getPrototypeOf() 比如 Reflect.defineProperty(target, propertyKey, attributes) 类似于 Object.defineProperty() 如果我们有 Object 可以做这些操作,那么为什么还需要有Reflect这样的新增对象呢? 这是因为在早期的 ECMA 规范中没有考虑到这种对 对象本身 的操作如何设计会更加规范,所以将这些 API 放到了 Object上面 但是 Object 作为一个构造函数,这些操作实际上放到它身上并不合适 另外还包含一些类似于 in、delete 操作符,让 JS 看起来是会有一些奇怪的 所以在 ES6 中新增了 Reflect,让我们这些操作都集中到了 Reflect 对象上 那么 Object 和 Reflect 对象之间的 API 关系,可以参考 MDN 文档: 比较 Reflect 和 Object 方法 3.2 Reflect 的常见方法 Reflect中有哪些常见的方法呢?它和Proxy是一一对应的,也是13个 Reflect.getPrototypeOf(target) 类似于 Object.getPrototypeOf() Reflect.setPrototypeOf(target, prototype) 设置对象原型的函数. 返回一个 Boolean, 如果更新成功,则返回 true Reflect.isExtensible(target) 类似于 Object.isExtensible() Reflect.preventExtensions(target) 类似于 Object.preventExtensions() , 返回一个 Boolean Reflect.getOwnPropertyDescriptor(target, propertyKey) 类似于 Object.getOwnPropertyDescriptor() , 如果对象中存在该属性,则返回对应的属性描述符, 否则返回 undefined Reflect.defineProperty(target, propertyKey, attributes) 和 Object.defineProperty() 类似, 如果设置成功就会返回 true Reflect.ownKeys(target) 返回一个包含所有自身属性(不包含继承属性)的数组 (类似于 Object.keys(), 但不会受 enumerable 影响) Reflect.has(target, propertyKey) 判断一个对象是否存在某个属性,和 in 运算符 的功能完全相同 Reflect.get(target, propertyKey[, receiver]) 获取对象身上某个属性的值,类似于 target[name] Reflect.set(target, propertyKey, value[, receiver]) 将值分配给属性的函数,返回一个 Boolean,如果更新成功,则返回 true Reflect.deleteProperty(target, propertyKey) 作为函数的 delete 操作符,相当于执行 delete target[name] Reflect.apply(target, thisArgument, argumentsList) 对一个函数进行调用操作,同时可以传入一个数组作为调用参数。和 Function.prototype.apply() 功能类似 Reflect.construct(target, argumentsList[, newTarget]) 对构造函数进行 new 操作,相当于执行 new target(...args) 3.3 Reflect 的使用 那么我们可以将之前Proxy案例中对原对象的操作,都修改为Reflect来操作 const obj = {name: 'why',age: 18}const objProxy = new Proxy(obj, {get: function (target, key) {console.log(监听到obj对象的${key}属性被访问了)return Reflect.get(target, key)// return target[key] // 对原来对象进行了直接操作},set: function (target, key, newValue) {console.log(监听到obj对象的${key}属性被设置值)Reflect.set(target, key, newValue)// target[key] = newValue // 对原来对象进行了直接操作} })objProxy.name = 'kobe'console.log(objProxy.name)/ 监听到obj对象的name属性被设置值监听到obj对象的name属性被访问了kobe/ 3.4 Receiver的作用 我们发现在使用getter、setter的时候有一个receiver的参数,它的作用是什么呢? 如果我们的源对象(obj)有 setter 、getter 的访问器属性,那么可以通过 receiver 来改变里面的 this const obj = {_name: 'why',get name() {return this._name // 不使用receiver, _name属性的操作不会被objProxy代理,因为this指向obj},set name(newValue) {this._name = newValue} }const objProxy = new Proxy(obj, {get: function (target, key, receiver) {// receiver 是创建出来的代理对象console.log('get 方法被访问-------', key, receiver)console.log(objProxy === receiver) // truereturn Reflect.get(target, key, receiver)},set: function (target, key, newValue, receiver) {Reflect.set(target, key, newValue, receiver)} })objProxy.name = 'kobe'console.log(objProxy.name) // kobe/ get 方法被访问------- name { _name: 'kobe', name: [Getter/Setter] }trueget 方法被访问------- _name { _name: 'kobe', name: [Getter/Setter] }truekobe/ 3.5 Reflect 的 construct function Student(name, age) {this.name = namethis.age = age}function Teacher() {}const stu = new Student('why', 18)console.log(stu)console.log(stu.__proto__ === Student.prototype)/ Student { name: 'why', age: 18 }true/// 执行 Student 函数中的内容,但是创建出来的对象是 Teacher 对象const teacher = Reflect.construct(Student, ['why', 18], Teacher)console.log(teacher)console.log(teacher.__proto__ === Teacher.prototype)/ Teacher { name: 'why', age: 18 }true/ 4. 响应式 4.1 什么是响应式? 先来看一下响应式意味着什么?我们来看一段代码: m 有一个初始化的值,有一段代码使用了这个值; 那么在 m 有一个新的值时,这段代码可以自动重新执行 let m = 0// 一段代码console.log(m)console.log(m 2)console.log(m 2)m = 200 上面的这样一种可以自动响应数据变量的代码机制,我们就称之为是响应式的 对象的响应式 4.2 响应式函数设计 首先,执行的代码中可能不止一行代码,所以我们可以将这些代码放到一个函数中: 那么问题就变成了,当数据发生变化时,自动去执行某一个函数; 但是有一个问题:在开发中是有很多的函数的,如何区分一个函数需要响应式,还是不需要响应式呢? 很明显,下面的函数中 foo 需要在 obj 的 name 发生变化时,重新执行,做出相应; bar 函数是一个完全独立于 obj 的函数,它不需要执行任何响应式的操作; // 对象的响应式const obj = {name: 'why',age: 18}function foo() {const newName = obj.nameconsole.log('你好啊,李银河')console.log('Hello World')console.log(obj.name)}function bar() {console.log('普通的其他函数')console.log('这个函数不需要有任何的响应式')}obj.name = 'kobe' // name 发生改变时候 foo 函数执行 响应式函数的实现 watchFn 如何区分响应式函数? 这个时候我们封装一个新的函数 watchFn 凡是传入到 watchFn 的函数,就是需要响应式的 其他默认定义的函数都是不需要响应式的 / 封装一个响应式的函数 /let reactiveFns = []function watchFn(fn) {reactiveFns.push(fn)}// 对象的响应式const obj = {name: 'why',age: 18}watchFn(function foo() {const newName = obj.nameconsole.log('你好啊,李银河')console.log('Hello World')console.log(obj.name)})watchFn(function demo() {console.log(obj.name, 'demo function ---------')})function bar() {console.log('普通的其他函数')console.log('这个函数不需要有任何的响应式')}obj.name = 'kobe' // name 发生改变时候 foo 函数执行reactiveFns.forEach((fn) => {fn()}) 4.3 响应式依赖的收集 目前收集的依赖是放到一个数组中来保存的,但是这里会存在数据管理的问题: 在实际开发中需要监听很多对象的响应式 这些对象需要监听的不只是一个属性,它们很多属性的变化,都会有对应的响应式函数 不可能在全局维护一大堆的数组来保存这些响应函数 所以要设计一个类,这个类用于管理某一个对象的某一个属性的所有响应式函数: 相当于替代了原来的简单 reactiveFns 的数组; class Depend {constructor() {this.reactiveFns = []}addDepend(reactiveFn) {this.reactiveFns.push(reactiveFn)}notify() {this.reactiveFns.forEach((fn) => {fn()})} }const depend = new Depend()function watchFn(fn) {depend.addDepend(fn)}// 对象的响应式const obj = {name: 'why', // depend 对象age: 18 // depend 对象}watchFn(function foo() {const newName = obj.nameconsole.log('你好啊,李银河')console.log('Hello World')console.log(obj.name)})watchFn(function demo() {console.log(obj.name, 'demo function ---------')})function bar() {console.log('普通的其他函数')console.log('这个函数不需要有任何的响应式')}obj.name = 'kobe'depend.notify() 4.4 监听对象的变化 那么接下来就可以通过之前的方式来监听对象的变化: 方式一:通过 Object.defineProperty 的方式(vue2采用的方式); 方式二:通过 new Proxy 的方式(vue3采用的方式); 我们这里先以Proxy的方式来监听 class Depend {constructor() {this.reactiveFns = []}addDepend(reactiveFn) {this.reactiveFns.push(reactiveFn)}notify() {this.reactiveFns.forEach((fn) => {fn()})} }const depend = new Depend()function watchFn(fn) {depend.addDepend(fn)}// 对象的响应式const obj = {name: 'why', // depend 对象age: 18 // depend 对象}// 监听对象的属性变化:Proxy(vue3)/Object.defineProperty(vue2)const objProxy = new Proxy(obj, {get: function (target, key, receiver) {return Reflect.get(target, key, receiver)},set: function (target, key, newValue, receiver) {Reflect.set(target, key, newValue, receiver)depend.notify()} })watchFn(function foo() {const newName = objProxy.nameconsole.log('你好啊,李银河')console.log('Hello World')console.log(objProxy.name)})watchFn(function demo() {console.log(objProxy.name, 'demo function ---------')})objProxy.name = 'kobe'objProxy.name = 'james'/ 你好啊,李银河Hello Worldkobekobe demo function ---------你好啊,李银河Hello Worldjamesjames demo function ---------/ 4.5 对象的依赖管理 目前是创建了一个 Depend 对象,用来管理对于 name 变化需要监听的响应函数: 但是实际开发中我们会有不同的对象,另外会有不同的属性需要管理; 如何可以使用一种数据结构来管理不同对象的不同依赖关系呢? 在前面我们刚刚学习过 WeakMap,并且在学习 WeakMap 的时候我讲到了后面通过 WeakMap 如何管理这种响应式的数据依赖: 实现 可以写一个 getDepend 函数专门来管理这种依赖关系 / 封装一个获取depend的函数 /const taregtMap = new WeakMap()function getDepend(target, key) {// 根据target对象获取mapconst map = taregtMap.get(target)if (!map) {map = new Map()taregtMap.set(target, map)}// 根据key获取depend对象const depend = map.get(key)if (!depend) {depend = new Depend()map.set(key, depend)}return depend}// 监听对象的属性变化:Proxy(vue3)/Object.defineProperty(vue2)const objProxy = new Proxy(obj, {get: function (target, key, receiver) {return Reflect.get(target, key, receiver)},set: function (target, key, newValue, receiver) {Reflect.set(target, key, newValue, receiver)const depend = getDepend(target, key)depend.notify()} }) 正确的依赖收集 我们之前收集依赖的地方是在 watchFn 中: 但是这种收集依赖的方式我们根本不知道是哪一个 key 的哪一个 depend 需要收集依赖; 只能针对一个单独的 depend 对象来添加你的依赖对象; 那么正确的应该是在哪里收集呢?应该在我们调用了 Proxy 的 get 捕获器时 因为如果一个函数中使用了某个对象的 key,那么它应该被收集依赖 / 封装一个响应式函数 /let activeReactviceFn = nullfunction watchFn(fn) {activeReactviceFn = fnfn()activeReactviceFn = null}/ 封装一个获取depend的函数 /const taregtMap = new WeakMap()function getDepend(target, key) {// 根据target对象获取maplet map = taregtMap.get(target)if (!map) {map = new Map()taregtMap.set(target, map)}// 根据key获取depend对象let depend = map.get(key)if (!depend) {depend = new Depend()map.set(key, depend)}return depend}// 监听对象的属性变化:Proxy(vue3)/Object.defineProperty(vue2)const objProxy = new Proxy(obj, {get: function (target, key, receiver) {// 根据 target key 获取对应的 depnedconst depend = getDepend(target, key)// 给 depend 对象中添加响应式函数activeReactviceFn && depend.addDepend(activeReactviceFn)return Reflect.get(target, key, receiver)},set: function (target, key, newValue, receiver) {Reflect.set(target, key, newValue, receiver)const depend = getDepend(target, key)depend.notify()} }) 4.6 对 Depend 重构 两个问题: 问题一:如果函数中有用到两次 key,比如 name,那么这个函数会被收集两次 问题二:我们并不希望将添加 reactiveFn 放到 get 中,因为它是属于 Depend 的行为 所以我们需要对 Depend 类进行重构: 解决问题一的方法:不使用数组,而是使用 Set 解决问题二的方法:添加一个新的方法,用于收集依赖 // 保存当前需要收集的响应式函数let activeReactviceFn = nullclass Depend {constructor() {this.reactiveFns = new Set()}depend() {if (activeReactviceFn) {this.reactiveFns.add(activeReactviceFn)} }addDepend(reactiveFn) {this.reactiveFns.add(reactiveFn)}notify() {this.reactiveFns.forEach((fn) => {fn()})} }// 对象的响应式const obj = {name: 'why', // depend 对象age: 18 // depend 对象}/ 封装一个响应式函数 /function watchFn(fn) {activeReactviceFn = fnfn()activeReactviceFn = null}/ 封装一个获取depend的函数 /const taregtMap = new WeakMap()function getDepend(target, key) {// 根据target对象获取maplet map = taregtMap.get(target)if (!map) {map = new Map()taregtMap.set(target, map)}// 根据key获取depend对象let depend = map.get(key)if (!depend) {depend = new Depend()map.set(key, depend)}return depend}// 监听对象的属性变化:Proxy(vue3)/Object.defineProperty(vue2)const objProxy = new Proxy(obj, {get: function (target, key, receiver) {// 根据 target key 获取对应的 depnedconst depend = getDepend(target, key)// 给 depend 对象中添加响应式函数depend.depend()return Reflect.get(target, key, receiver)},set: function (target, key, newValue, receiver) {Reflect.set(target, key, newValue, receiver)const depend = getDepend(target, key)depend.notify()} })watchFn(function () {console.log(objProxy.name, '--------------')console.log(objProxy.name, '++++++++++++++')})objProxy.name = 'kobe'/ why --------------why ++++++++++++++kobe --------------kobe ++++++++++++++/ 4.7 创建响应式对象 目前的响应式是针对于obj一个对象的,我们可以创建出来一个函数,针对所有的对象都可以变成响应式对象 / 保存当前需要收集的响应式函数 /let activeReactviceFn = null/ 依赖收集类 /class Depend {constructor() {this.reactiveFns = new Set()}depend() {if (activeReactviceFn) {this.reactiveFns.add(activeReactviceFn)} }addDepend(reactiveFn) {this.reactiveFns.add(reactiveFn)}notify() {this.reactiveFns.forEach((fn) => {fn()})} }/ 封装一个响应式函数 /function watchFn(fn) {activeReactviceFn = fnfn()activeReactviceFn = null}/ 封装一个获取depend的函数 /const taregtMap = new WeakMap()function getDepend(target, key) {// 根据target对象获取maplet map = taregtMap.get(target)if (!map) {map = new Map()taregtMap.set(target, map)}// 根据key获取depend对象let depend = map.get(key)if (!depend) {depend = new Depend()map.set(key, depend)}return depend}/ 创建响应式对象函数 /function reactive(obj) {// 监听对象的属性变化:Proxy(vue3)/Object.defineProperty(vue2)return new Proxy(obj, {get: function (target, key, receiver) {// 根据 target key 获取对应的 depnedconst depend = getDepend(target, key)// 给 depend 对象中添加响应式函数depend.depend()return Reflect.get(target, key, receiver)},set: function (target, key, newValue, receiver) {Reflect.set(target, key, newValue, receiver)const depend = getDepend(target, key)depend.notify()} })}const info = reactive({address: '广州市',height: 1.88})watchFn(() => {console.log(info.address, '---')})info.address = '北京市' 4.8 Vue2 响应式原理 前面所实现的响应式的代码,其实就是 Vue3 中的响应式原理: Vue3 主要是通过 Proxy 来监听数据的变化以及收集相关的依赖的 Vue2 中通过 Object.defineProerty的方式来实现对象属性的监听 可以将 reactive 函数进行如下的重构: 在传入对象时,我们可以遍历所有的 key,并且通过属性存储描述符来监听属性的获取和修改 在 setter 和 getter 方法中的逻辑和前面的 Proxy 是一致的 / 保存当前需要收集的响应式函数 /let activeReactviceFn = null/ 依赖收集类 /class Depend {constructor() {this.reactiveFns = new Set()}depend() {if (activeReactviceFn) {this.reactiveFns.add(activeReactviceFn)} }addDepend(reactiveFn) {this.reactiveFns.add(reactiveFn)}notify() {this.reactiveFns.forEach((fn) => {fn()})} }/ 封装一个响应式函数 /function watchFn(fn) {activeReactviceFn = fnfn()activeReactviceFn = null}/ 封装一个获取depend的函数 /const taregtMap = new WeakMap()function getDepend(target, key) {// 根据target对象获取maplet map = taregtMap.get(target)if (!map) {map = new Map()taregtMap.set(target, map)}// 根据key获取depend对象let depend = map.get(key)if (!depend) {depend = new Depend()map.set(key, depend)}return depend}/ 创建响应式对象函数 /function reactive(obj) {Object.keys(obj).forEach((key) => {let value = obj[key]Object.defineProperty(obj, key, {get: function () {const dep = getDepend(obj, key)dep.depend()return value},set: function (newValue) {value = newValueconst dep = getDepend(obj, key)dep.notify()} })})return obj}const info = reactive({address: '广州市',height: 1.88})watchFn(() => {console.log(info.address, '---')})info.address = '北京市' 本篇文章为转载内容。原文链接:https://blog.csdn.net/wanghuan1020/article/details/126774033。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-01-11 12:37:47
679
转载
转载文章
...日志数据,提取出某日访问百度次数最多的那个IP 解决思想: hash分解+ 分而治之 + 归并 把这一天访问百度的日志中的IP取出来,逐个写入到一个大文件中。注意到IP是32位的,最多有2^32个IP。同样可以采用hash映射的方法,比如模1024,把整个大文件映射为1024个小文件。 再找出每个小文中出现频率最大的IP(可以采用hash_map进行频率统计,然后再找出频率最大的几个)及相应的频率。 然后再在这1024组最大的IP中,找出那个频率最大的IP,即为所求。 问题5 海量数据分布在100台电脑中,想个办法高效统计出这批数据的TOP10。 解决思想: 分而治之 + 归并。 注意TOP10是取最大值或最小值。如果取频率TOP10,就应该先hash分解。 在每台电脑上求出TOP10,采用包含10个元素的堆完成(TOP10小,用最大堆,TOP10大,用最小堆)。比如求TOP10大,我们首先取前10个元素调整成最小堆,如果发现,然后扫描后面的数据,并与堆顶元素比较,如果比堆顶元素大,那么用该元素替换堆顶,然后再调整为最小堆。最后堆中的元素就是TOP10大。 求出每台电脑上的TOP10后,然后把这100台电脑上的TOP10组合起来,共1000个数据,再利用上面类似的方法求出TOP10就可以了。 问题6 在2.5亿个整数中找出不重复的整数,内存不足以容纳这2.5亿个整数。 解决思路1 : hash 分解+ 分而治之 + 归并 2.5亿个int数据hash到1024个小文件中a0~a1023,如果某个小文件大小还大于内存,进行多级hash。每个小文件读进内存,找出只出现一次的数据,输出到b0~b1023。最后数据合并即可。 解决思路2 : 2-Bitmap 如果内存够1GB的话,采用2-Bitmap(每个数分配2bit,00表示不存在,01表示出现一次,10表示多次,11无意义)进行,共需内存2^322bit=1GB内存。然后扫描这2.5亿个整数,查看Bitmap中相对应位,如果是00变01,01变10,10保持不变。所描完事后,查看bitmap,把对应位是01的整数输出即可。 注意,如果是找出重复的数据,可以用1-bitmap。第一次bit位由0变1,第二次查询到相应bit位为1说明是重复数据,输出即可。 问题7 一共有N个机器,每个机器上有N个数。每个机器最多存O(N)个数并对它们操作。如何找到N^2个数中的中数? 解决思想1 : hash分解 + 排序 按照升序顺序把这些数字,hash划分为N个范围段。假设数据范围是2^32 的unsigned int 类型。理论上第一台机器应该存的范围为0~(2^32)/N,第i台机器存的范围是(2^32)(i-1)/N~(2^32)i/N。hash过程可以扫描每个机器上的N个数,把属于第一个区段的数放到第一个机器上,属于第二个区段的数放到第二个机器上,…,属于第N个区段的数放到第N个机器上。注意这个过程每个机器上存储的数应该是O(N)的。 然后我们依次统计每个机器上数的个数,一次累加,直到找到第k个机器,在该机器上累加的数大于或等于(N^2)/2,而在第k-1个机器上的累加数小于(N^2)/2,并把这个数记为x。那么我们要找的中位数在第k个机器中,排在第(N^2)/2-x位。然后我们对第k个机器的数排序,并找出第(N^2)/2-x个数,即为所求的中位数的复杂度是O(N^2)的。 解决思想2: 分而治之 + 归并 先对每台机器上的数进行排序。排好序后,我们采用归并排序的思想,将这N个机器上的数归并起来得到最终的排序。找到第(N^2)/2个便是所求。复杂度是O(N^2 lgN^2)的。 2 Trie树+红黑树+hash_map 这里Trie树木、红黑树或者hash_map可以认为是第一部分中分而治之算法的具体实现方法之一。 问题1 上千万或上亿数据(有重复),统计其中出现次数最多的钱N个数据。 解决思路: 红黑树 + 堆排序 如果是上千万或上亿的int数据,现在的机器4G内存可以能存下。所以考虑采用hash_map/搜索二叉树/红黑树等来进行统计重复次数。 然后取出前N个出现次数最多的数据,可以用包含N个元素的最小堆找出频率最大的N个数据。 问题2 1000万字符串,其中有些是重复的,需要把重复的全部去掉,保留没有重复的字符串。请怎么设计和实现? 解决思路:trie树。 这题用trie树比较合适,hash_map也应该能行。 问题3 一个文本文件,大约有一万行,每行一个词,要求统计出其中最频繁出现的前10个词,请给出思想,给出时间复杂度分析。 解决思路: trie树 + 堆排序 这题是考虑时间效率。 1. 用trie树统计每个词出现的次数,时间复杂度是O(nlen)(len表示单词的平准长度)。 2. 然后找出出现最频繁的前10个词,可以用堆来实现,前面的题中已经讲到了,时间复杂度是O(nlg10)。 总的时间复杂度,是O(nle)与O(nlg10)中较大的哪一个。 问题4 搜索引擎会通过日志文件把用户每次检索使用的所有检索串都记录下来,每个查询串的长度为1-255字节。假设目前有一千万个记录,这些查询串的重复读比较高,虽然总数是1千万,但是如果去除重复和,不超过3百万个。一个查询串的重复度越高,说明查询它的用户越多,也就越热门。请你统计最热门的10个查询串,要求使用的内存不能超过1G。 解决思想 : trie树 + 堆排序 采用trie树,关键字域存该查询串出现的次数,没有出现为0。最后用10个元素的最小推来对出现频率进行排序。 3 BitMap或者Bloom Filter 3.1 BitMap BitMap说白了很easy,就是通过bit位为1或0来标识某个状态存不存在。可进行数据的快速查找,判重,删除,一般来说适合的处理数据范围小于82^32。否则内存超过4G,内存资源消耗有点多。 问题1 已知某个文件内包含一些电话号码,每个号码为8位数字,统计不同号码的个数。 解决思路: bitmap 8位最多99 999 999,需要100M个bit位,不到12M的内存空间。我们把0-99 999 999的每个数字映射到一个Bit位上,所以只需要99M个Bit==12MBytes,这样,就用了小小的12M左右的内存表示了所有的8位数的电话 问题2 2.5亿个整数中找出不重复的整数的个数,内存空间不足以容纳这2.5亿个整数。 解决思路:2bit map 或者两个bitmap。 将bit-map扩展一下,用2bit表示一个数即可,00表示未出现,01表示出现一次,10表示出现2次及以上,11可以暂时不用。 在遍历这些数的时候,如果对应位置的值是00,则将其置为01;如果是01,将其置为10;如果是10,则保持不变。需要内存大小是2^32/82=1G内存。 或者我们不用2bit来进行表示,我们用两个bit-map即可模拟实现这个2bit-map,都是一样的道理。 3.2 Bloom filter Bloom filter可以看做是对bit-map的扩展。 参考july大神csdn文章 Bloom Filter 详解 4 Hadoop+MapReduce 参考引用july大神 csdn文章 MapReduce的初步理解 Hadoop框架与MapReduce模式 转载请注明本文地址: 大数据——海量数据处理的基本方法总结 本篇文章为转载内容。原文链接:https://blog.csdn.net/hong2511/article/details/80842704。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2024-03-01 12:40:17
541
转载
转载文章
...类指令通常涉及到诸如访问和修改关键系统资源(如内存管理、中断控制器、任务调度等)的操作。在本文的上下文中,如果用户态的应用程序尝试执行特权指令,系统将触发中断,防止非授权访问内核资源,确保操作系统底层的安全性不受侵害。
2023-03-14 19:08:07
254
转载
Spark
...只会在特定阶段被频繁访问,而在其他阶段则很少用到。要是你提前把这部分数据缓存了,不光白白占用了宝贵的内存空间,搞不好后面真要用缓存的地方还找不到足够的空位呢! 因此,合理规划缓存策略非常重要。比如说,在某个任务快开始了,你再随手调用一下.cache()这个方法,这样就能保证数据乖乖地待在内存里,别到时候卡壳啦! 三、实践案例 如何正确使用分布式缓存? 接下来,我想分享几个具体的案例,帮助大家更好地理解和运用分布式缓存。 案例1:简单的词频统计 假设我们有一个文本文件,里面包含了大量的英文单词。我们的目标是统计每个单词出现的次数。为了提高效率,我们可以先将文件内容缓存起来,然后再进行处理。 scala val textFile = sc.textFile("hdfs://path/to/input.txt") textFile.cache() val wordCounts = textFile.flatMap(_.split(" ")) .map(word => (word, 1)) .reduceByKey(_ + _) wordCounts.collect().foreach(println) 在这个例子中,.cache()方法确保了textFile RDD的内容只被加载一次,并且可以被后续的操作共享。其实嘛,要是没用缓存的话,每次你调用flatMap或者map的时候,都得重新去原始数据里翻一遍,这就跟每次出门都得把家里所有东西再检查一遍似的,纯属给自己找麻烦啊! 案例2:多步骤处理流程 有时候,一个任务可能会涉及到多个阶段的处理,比如过滤、映射、聚合等等。在这种情况下,合理安排缓存的位置尤为重要。 python from pyspark.sql import SparkSession spark = SparkSession.builder.appName("WordCount").getOrCreate() df = spark.read.text("hdfs://path/to/input.txt") 第一步:将文本拆分为单词 words = df.selectExpr("split(value, ' ') as words").select("words.") 第二步:缓存中间结果 words.cache() 第三步:统计每个单词的出现次数 word_counts = words.groupBy("value").count() word_counts.show() 这里,我们在第一步处理完之后立即调用了.cache()方法,目的是为了保留中间结果,方便后续步骤复用。要是不这么干啊,那每走一步都得把上一步的算一遍,想想就费劲,效率肯定低得让人抓狂。 四、总结与展望 通过今天的讨论,相信大家对Spark的分布式缓存有了更深刻的认识。虽然它能带来显著的性能提升,但也并非万能药。其实啊,要想把它用得溜、用得爽,就得先搞懂它是怎么工作的,再根据具体的情况去灵活调整。不然的话,它的那些本事可就都浪费啦! 未来,随着硬件条件的不断改善以及算法优化的持续推进,相信Spark会在更多领域展现出更加卓越的表现。嘿,咱们做开发的嘛,就得有颗永远好奇的心!就跟追剧似的,新技术一出就得赶紧瞅两眼,说不定哪天就用上了呢。别怕麻烦,多学点东西总没错,说不定哪天就能整出个大招儿来! 最后,感谢大家耐心阅读这篇文章。如果你有任何疑问或者想法,欢迎随时交流!让我们一起努力,共同进步吧!
2025-05-02 15:46:14
82
素颜如水
转载文章
...安全认证。 3.用户访问服务器的时候,服务器的某一个服务程序把用户的请求发送到PAM模块进行认证。不同的应用程序所对应的PAM模块也是不同的。 4.如果想查看某个程序是否支持PAM认证,可以用ls命令进行查看/etc/pam.d/。 ls /etc/pam.d/ | grep su 5.PAM的配置文件中的每一行都是一个独立的认证过程,它们按从上往下的顺序依次由PAM模块调用。 4.PAM安全认证流程 控制类型也称做Control Flags,用于PAM验证类型的返回结果 用户1 用户2 用户3 用户4 auth required 模块1 pass fail pass pass auth sufficient 模块2 pass pass fail pass auth required 模块3 pass pass pass fail 结果 pass fail pass pass 4 五.限制使用su命令的用户(pam-wheel认证模块) 1.su命令概述: 通过su命令可以非常方便切换到另一个用户,但前提条件是必须知道用户登录密码。对于生产环境中的Linux服务器,每多一个人知道特权密码,安全风险就多一分。于是就多了一种折中的办法,使用sudo命令提升执行权限,不过需要由管理员预先进行授权, 指定用户使用某些命令: 2. su命令的用途以及用法: 用途:以其他用户身份(如root)执行授权命令用法:sudo 授权命令 3.配置su的授权(加入wheel组)(pam_wheel认证模块:): 进入授权命令:1.visudo 或者 vim /etc/sudoers语法格式:1.用户 主机名=命令程序列表2.用户 主机名=(用户)命令程序列表-l:列出用户在主机上可用的和被禁止的命令;一般配置好/etc/sudoers后,要用这个命令来查看和测试是不是配置正确的;-v:验证用户的时间戳;如果用户运行sudo后,输入用户的密码后,在短时间内可以不用输入口令来直接进行sudo操作;用-v可以跟踪最新的时间戳;-u:指定以以某个用户执行特定操作;-k:删除时间戳,下一个sudo命令要求用求提供密码; 1.首先创建3个组 2.vim /etc/pam.d/su把第六行注释去掉保存退出 1. 以上两行是默认状态(即开启第一行,注释第二行),这种状态下是允许所有用户间使用su命令进行切换的 2.两行都注释也是运行所有用户都能使用su命令,但root下使用su切换到其他普通用户需要输入密码: 3.如果第–行不注释,则root 使用su切换普通用户就不需要输入密码( pam_ rootok. so模块的主要作用是使uid为0的用户,即root用户能够直接通过认证而不用输入密码。) 4.如果开启第二行,表示只有root用户和wheel1组内的用户才可以使用su命令。 5.如果注释第一行,开启第二行,表示只有whee1组内的用户才能使用su命令,root用户也被禁用su命令。 3.将liunan加入到wheel之后,hehe就有了使用su命令的权限 4.使用pam_wheel认证后,没有在wheel里的用户都不能再用su 5.whoami命令确定当前用户是谁 4.配置/etc/sudoers文件(授权用户较多的时候使用): visudo单个授权visudo 或者 vim /etc/sudoers记录格式:user MACHINE=COMMANDS可以使用通配符“ ”号任意值和“ !”号进行取反操作。%组名代表一整个组权限生效后,输入密码后5分钟可以不用重新输入密码。例如:visudo命令下user kiro=(root)NOPASSWD:/usr/sbin/useradd,PASSWD:/usr/sbin/usermod代表 kiro主机里的user用户,可以无密码使用useradd命令,有密码使用usermod/etc/sudoers多个授权Host_Alias MYHOST= localhost 主机别名:主机名、IP、网络地址、其他主机别名!取反Host_Alias MAILSVRS=smtp,pop(主机名)User_Alias MYUSER = kiro,user1,lisi 用户别名:包含用户、用户组(%组名(使用引导))、还可以包含其他其他已经用户的别名User_Alias OPERATORS=zhangsan,tom,lisi(需要授权的用户)Cmnd_Alias MYCMD = /sbin/,/usr/bin/passwd 命令路劲、目录(此目录内的所有命令)、其他事先定义过的命令别名Cmnd_Alias PKGTOOLS=/bin/rpm,/usr/bin/yum(授权)MYUSER MYHOST = NOPASSWD : MYCMDDS 授权格式sudo -l 查询目前sudo操作查看sudo操作记录需启用Defaults logfile配置默认日志文件: /var/log/sudosudo -l 查看当前用户获得哪些sudo授权(启动日志文件后,sudo操作过程才会被记录) 1.首先用visudo 或者 vim /etc/sudoers进入,输入需要授权的命令 2.切换到taojian用户,因为设置了它不能使用创建用户的命令所以无法创建 六.开关机安全控制 1.调整BIOS引导设置 1.将第一引导设备设为当前系统所在硬盘2.禁止从其他设备(光盘、U盘、网络)引导系统3.将安全级别设为setup,并设置管理员密码 2.GRUB限制 1.使用grub2-mkpasswd-pbkdf2生成密钥2.修改/etclgrub.d/00_header文件中,添加密码记录3.生成新的grub.cfg配置文件 方法一: 通常情况下在系统开机进入GRUB菜单时,按e键可以查看并修改GRUB引导参数,这对服务器是一个极大的威胁。可以为GRUB菜单设置一个密码,只有提供正确的密码才被允许修改引导参数。grub2-mkpasswd-pbkdf2 根据提示设置GRUB菜单的密码PBKDF2 hash of your password is grub.pbkd..... 省略部分内容为经过加密生成的密码字符串cp /boot/grub2/grub.cfg /boot/grub2/grub.cfg.bak 8cp /etc/grub.d/00_header /etc/grub.d/00_header.bak 9vim /etc/grub.d/00_headercat << EOFset superusers="root" 设置用户名为rootpassword_pbkdf2 root grub.pbkd2..... 设置密码,省略部分内容为经过加密生成的密码字符串EOF16grub2-mkconfig -o /boot/grub2/grub.cfg 生成新的grub.cfg文件重启系统进入GRUB菜单时,按e键将需要输入账号密码才能修改引导参数。 方法二: 1.一步到位2.grub2-setpassword 七.终端以及登录控制 1.限制root只在安全终端登录 安全终端配置文件在 /etc/securetty 2..禁止普通用户登录 1.建立/etc/nologin文件 2.删除nologin文件或重启后即恢复正常 vim /etc/securetty在端口前加号拒绝访问touch /etc/nologin 禁止普通用户登录rm -rf /etc/nologin 取消禁止 八.系统弱口令检测 1.JOHN the Ripper,简称为JR 1.一款密码分析工具,支持字典式的暴力破解2.通过对shadow文件的口令分析,可以检测密码强度3.官网网站:http://www.openwall.com/john/ 2.安装弱口令账号 1.获得Linux/Unix服务器的shadow文件2.执行john程序,讲shadow文件作为参数 3.密码文件的暴力破解 1.准备好密码字典文件,默认为password.lst2.执行john程序,结合--wordlist=字典文件 九.网络端口扫描 1.NMAP 1.—款强大的网络扫描、安全检测工具,支持ping扫描,多端口检测等多种技术。2.官方网站: http://nmap.orgl3.CentOS 7.3光盘中安装包,nmap-6.40-7.el7.x86_64.rpm 2.格式 NMAP [扫描类型] [选项] <扫描目标....> 安装NMAP软件包rpm -qa | grep nmapyum install -y nmapnmap命令常用的选项和扫描类型-p:指定扫描的端口。-n:禁用反向DNS 解析 (以加快扫描速度)。-sS:TCP的SYN扫描(半开扫描),只向目标发出SYN数据包,如果收到SYN/ACK响应包就认为目标端口正在监听,并立即断开连接;否则认为目标端口并未开放。-sT:TCP连接扫描,这是完整的TCP扫描方式(默认扫描类型),用来建立一个TCP连接,如果成功则认为目标端口正在监听服务,否则认为目标端口并未开放。-sF:TCP的FIN扫描,开放的端口会忽略这种数据包,关闭的端口会回应RST数据包。许多防火墙只对SYN数据包进行简单过滤,而忽略了其他形式的TCP attack 包。这种类型的扫描可间接检测防火墙的健壮性。-sU:UDP扫描,探测目标主机提供哪些UDP服务,UDP扫描的速度会比较慢。-sP:ICMP扫描,类似于ping检测,快速判断目标主机是否存活,不做其他扫描。-P0:跳过ping检测,这种方式认为所有的目标主机是存活的,当对方不响应ICMP请求时,使用这种方式可以避免因无法 ping通而放弃扫描。 总结: 1.账号基本安全措施:系统账号处理、密码安全控制、命令历史清理、自动注销 2.用户切换与提权(su、sudo) 3.开关机安全控制(BIOS引导设置、禁止Ctrl+Alt+Del快捷键、GRUB菜单设置密码) 4.终端控制 5.弱口令检测——John the Ripper 6.端口扫描——namp 本篇文章为转载内容。原文链接:https://blog.csdn.net/weixin_67474417/article/details/123982900。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-05-07 23:37:44
96
转载
转载文章
...关系进行映射,那么当访问ObjA的时候,顺便访问这个额外的指针,从这个指针指向的位图就能找到被ObjA引用的分区B对应的内存块。通常我们只需要判定位图里面对应的位是否有1,有的话则认为发生了引用。 class CardTable: public CHeapObj<mtGC> {friend class VMStructs;public:typedef uint8_t CardValue;// All code generators assume that the size of a card table entry is one byte.// They need to be updated to reflect any change to this.// This code can typically be found by searching for the byte_map_base() method.STATIC_ASSERT(sizeof(CardValue) == 1);protected:// The declaration order of these const fields is important; see the// constructor before changing.const MemRegion _whole_heap; // the region covered by the card tableconst size_t _page_size; // page size used when mapping _byte_mapsize_t _byte_map_size; // in bytesCardValue _byte_map; // the card marking arrayCardValue _byte_map_base;// Some barrier sets create tables whose elements correspond to parts of// the heap; the CardTableBarrierSet is an example. Such barrier sets will// normally reserve space for such tables, and commit parts of the table// "covering" parts of the heap that are committed. At most one covered// region per generation is needed.static constexpr int max_covered_regions = 2;// The covered regions should be in address order.MemRegion _covered[max_covered_regions];// The last card is a guard card; never committed.MemRegion _guard_region;inline size_t compute_byte_map_size(size_t num_bytes);enum CardValues {clean_card = (CardValue)-1,dirty_card = 0,CT_MR_BS_last_reserved = 1};// a word's worth (row) of clean card valuesstatic const intptr_t clean_card_row = (intptr_t)(-1);// CardTable entry sizestatic uint _card_shift;static uint _card_size;static uint _card_size_in_words;size_t last_valid_index() const {return cards_required(_whole_heap.word_size()) - 1;}private:void initialize_covered_region(void region0_start, void region1_start);MemRegion committed_for(const MemRegion mr) const;public:CardTable(MemRegion whole_heap);virtual ~CardTable() = default;void initialize(void region0_start, void region1_start);// Barrier set functions.// Initialization utilities; covered_words is the size of the covered region// in, um, words.inline size_t cards_required(size_t covered_words) const {assert(is_aligned(covered_words, _card_size_in_words), "precondition");return covered_words / _card_size_in_words;}// Dirty the bytes corresponding to "mr" (not all of which must be// covered.)void dirty_MemRegion(MemRegion mr);// Clear (to clean_card) the bytes entirely contained within "mr" (not// all of which must be covered.)void clear_MemRegion(MemRegion mr);// Return true if "p" is at the start of a card.bool is_card_aligned(HeapWord p) {CardValue pcard = byte_for(p);return (addr_for(pcard) == p);}// Mapping from address to card marking array entryCardValue byte_for(const void p) const {assert(_whole_heap.contains(p),"Attempt to access p = " PTR_FORMAT " out of bounds of "" card marking array's _whole_heap = [" PTR_FORMAT "," PTR_FORMAT ")",p2i(p), p2i(_whole_heap.start()), p2i(_whole_heap.end()));CardValue result = &_byte_map_base[uintptr_t(p) >> _card_shift];assert(result >= _byte_map && result < _byte_map + _byte_map_size,"out of bounds accessor for card marking array");return result;}// The card table byte one after the card marking array// entry for argument address. Typically used for higher bounds// for loops iterating through the card table.CardValue byte_after(const void p) const {return byte_for(p) + 1;}void invalidate(MemRegion mr);// Provide read-only access to the card table array.const CardValue byte_for_const(const void p) const {return byte_for(p);}const CardValue byte_after_const(const void p) const {return byte_after(p);}// Mapping from card marking array entry to address of first wordHeapWord addr_for(const CardValue p) const {assert(p >= _byte_map && p < _byte_map + _byte_map_size,"out of bounds access to card marking array. p: " PTR_FORMAT" _byte_map: " PTR_FORMAT " _byte_map + _byte_map_size: " PTR_FORMAT,p2i(p), p2i(_byte_map), p2i(_byte_map + _byte_map_size));// As _byte_map_base may be "negative" (the card table has been allocated before// the heap in memory), do not use pointer_delta() to avoid the assertion failure.size_t delta = p - _byte_map_base;HeapWord result = (HeapWord) (delta << _card_shift);assert(_whole_heap.contains(result),"Returning result = " PTR_FORMAT " out of bounds of "" card marking array's _whole_heap = [" PTR_FORMAT "," PTR_FORMAT ")",p2i(result), p2i(_whole_heap.start()), p2i(_whole_heap.end()));return result;}// Mapping from address to card marking array index.size_t index_for(void p) {assert(_whole_heap.contains(p),"Attempt to access p = " PTR_FORMAT " out of bounds of "" card marking array's _whole_heap = [" PTR_FORMAT "," PTR_FORMAT ")",p2i(p), p2i(_whole_heap.start()), p2i(_whole_heap.end()));return byte_for(p) - _byte_map;}CardValue byte_for_index(const size_t card_index) const {return _byte_map + card_index;}// Resize one of the regions covered by the remembered set.void resize_covered_region(MemRegion new_region);// Card-table-RemSet-specific things.static uintx ct_max_alignment_constraint();static uint card_shift() {return _card_shift;}static uint card_size() {return _card_size;}static uint card_size_in_words() {return _card_size_in_words;}static constexpr CardValue clean_card_val() { return clean_card; }static constexpr CardValue dirty_card_val() { return dirty_card; }static intptr_t clean_card_row_val() { return clean_card_row; }// Initialize card sizestatic void initialize_card_size();// Card marking array base (adjusted for heap low boundary)// This would be the 0th element of _byte_map, if the heap started at 0x0.// But since the heap starts at some higher address, this points to somewhere// before the beginning of the actual _byte_map.CardValue byte_map_base() const { return _byte_map_base; }virtual bool is_in_young(const void p) const = 0;}; class G1CardTable : public CardTable {friend class VMStructs;friend class G1CardTableChangedListener;G1CardTableChangedListener _listener;public:enum G1CardValues {g1_young_gen = CT_MR_BS_last_reserved << 1,// During evacuation we use the card table to consolidate the cards we need to// scan for roots onto the card table from the various sources. Further it is// used to record already completely scanned cards to avoid re-scanning them// when incrementally evacuating the old gen regions of a collection set.// This means that already scanned cards should be preserved.//// The merge at the start of each evacuation round simply sets cards to dirty// that are clean; scanned cards are set to 0x1.//// This means that the LSB determines what to do with the card during evacuation// given the following possible values://// 11111111 - clean, do not scan// 00000001 - already scanned, do not scan// 00000000 - dirty, needs to be scanned.//g1_card_already_scanned = 0x1};static const size_t WordAllClean = SIZE_MAX;static const size_t WordAllDirty = 0;STATIC_ASSERT(BitsPerByte == 8);static const size_t WordAlreadyScanned = (SIZE_MAX / 255) g1_card_already_scanned;G1CardTable(MemRegion whole_heap): CardTable(whole_heap), _listener() {_listener.set_card_table(this);}static CardValue g1_young_card_val() { return g1_young_gen; }static CardValue g1_scanned_card_val() { return g1_card_already_scanned; }void verify_g1_young_region(MemRegion mr) PRODUCT_RETURN;void g1_mark_as_young(const MemRegion& mr);size_t index_for_cardvalue(CardValue const p) const {return pointer_delta(p, _byte_map, sizeof(CardValue));}// Mark the given card as Dirty if it is Clean. Returns whether the card was// Clean before this operation. This result may be inaccurate as it does not// perform the dirtying atomically.inline bool mark_clean_as_dirty(CardValue card);// Change Clean cards in a (large) area on the card table as Dirty, preserving// already scanned cards. Assumes that most cards in that area are Clean.inline void mark_range_dirty(size_t start_card_index, size_t num_cards);// Change the given range of dirty cards to "which". All of these cards must be Dirty.inline void change_dirty_cards_to(CardValue start_card, CardValue end_card, CardValue which);inline uint region_idx_for(CardValue p);static size_t compute_size(size_t mem_region_size_in_words) {size_t number_of_slots = (mem_region_size_in_words / _card_size_in_words);return ReservedSpace::allocation_align_size_up(number_of_slots);}// Returns how many bytes of the heap a single byte of the Card Table corresponds to.static size_t heap_map_factor() { return _card_size; }void initialize(G1RegionToSpaceMapper mapper);bool is_in_young(const void p) const override;}; 以位为粒度的位图能准确描述每一个字的引用关系,但是一个位通常包含的信息太少,只能描述2个状态:引用还是未引用。实际应用中JVM在垃圾回收的时候需要更多的状态,如果增加至一个字节来描述状态,则位图需要256KB的空间,这个数字太大,开销占了25%。所以一个可能的做法位图不再描述一个字,而是一个区域,JVM选择512字节为单位,即用一个字节描述512字节的引用关系。选择一个区域除了空间利用率的问题之外,实际上还有现实的意义。我们知道Java对象实际上不是一个字能描述的(有一个参数可以控制对象最小对齐的大小,默认是8字节,实际上Java在JVM中还有一些附加信息,所以对齐后最小的Java对象是16字节),很多Java对象可能是几十个字节或者几百个字节,所以用一个字节描述一个区域是有意义的。但是我没有找到512的来源,为什么512效果最好?没有相应的数据来支持这个数字,而且这个值不可以配置,不能修改,但是有理由相信512字节的区域是为了节约内存额外开销。按照这个值,1MB的内存只需要2KB的额外空间就能描述引用关系。这又带来另一个问题,就是512字节里面的内存可能被引用多次,所以这是一个粗略的关系描述,那么在使用的时候需要遍历这512字节。 再举一个例子,假设有两个对象B、C都在这512字节的区域内。为了方便处理,记录对象引用关系的时候,都使用对象的起始位置,然后用这个地址和512对齐,因此B和C对象的卡表指针都指向这一个卡表的位置。那么对于引用处理也有可有两种处理方法:·处理的时候会以堆分区为处理单位,遍历整个堆分区,在遍历的时候,每次都会以对象大小为步长,结合卡表,如果该卡表中对应的位置被设置,则说明对象和其他分区的对象发生了引用。具体内容在后文中介绍Refine的时候还会详细介绍。·处理的时候借助于额外的数据结构,找到真正对象的位置,而不需要从头开始遍历。在后文的并发标记处理时就使用了这种方法,用于找到第一个对象的起始位置。在G1除了512字节粒度的卡表之外,还有bitMap,例如使用bitMap可以描述一个分区对另外一个分区的引用情况。在JVM中bitMap使用非常多,例如还可以描述内存的分配情况。 在G1除了512字节粒度的卡表之外,还有bitMap,例如使用bitMap可以描述一个分区对另外一个分区的引用情况。在JVM中bitMap使用非常多,例如还可以描述内存的分配情况。G1在混合收集算法中用到了并发标记。在并发标记的时候使用了bitMap来描述对象的分配情况。例如1MB的分区可以用16KB(16KB×ObjectAlignmentInBytes×8=1MB)来描述,即16KB额外的空间。其中ObjectAlignmentInBytes是8字节,指的是对象对齐,第二个8是指一个字节有8位。即每一个位可以描述64位。例如一个对象长度对齐之后为24字节,理论上它占用3个位来描述这个24字节已被使用了,实际上并不需要,在标记的时候只需要标记这3个位中的第一个位,再结合堆分区对象的大小信息就能准确找出。其最主要的目的是为了效率,标记一个位和标记3个位相比能节约不少时间,如果对象很大,则更划算。这些都是源码的实现细节,大家在阅读源码时需要细细斟酌。 本篇文章为转载内容。原文链接:https://blog.csdn.net/qq_16500963/article/details/132133125。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-12-16 20:37:50
246
转载
转载文章
...起。结果就是作业不能访问 Web 服务器的内部函数;例如,在使用 WebSphere 应用服务器时,由 Quartz 调度的作业并不能影响服务器的动态缓存和数据源。 二、java中实现定时任务分类 从实现的技术上来分类,目前主要有三种技术(或者说有三种产品): Java自带的java.util.Timer类,这个类允许你调度一个java.util.TimerTask任务。使用这种方式可以让你的程序按照某一个频度执行,但不能在指定时间运行。一般用的较少,这篇文章将不做详细介绍。 使用Quartz,这是一个功能比较强大的的调度器,可以让你的程序在指定时间执行,也可以按照某一个频度执行,配置起来稍显复杂,稍后会详细介绍。 Spring3.0以后自带的task,可以将它看成一个轻量级的Quartz,而且使用起来比Quartz简单许多,稍后会介绍。 从作业类的继承方式来讲,可以分为两类: 作业类需要继承自特定的作业类基类,如Quartz中需要继承自org.springframework.scheduling.quartz.QuartzJobBean;java.util.Timer中需要继承自java.util.TimerTask。 作业类即普通的java类,不需要继承自任何基类。 注:个人推荐使用第二种方式,因为这样所以的类都是普通类,不需要事先区别对待。 从任务调度的触发时机来分,这里主要是针对作业使用的触发器,主要有以下两种: 每隔指定时间则触发一次,在Quartz中对应的触发器为:org.springframework.scheduling.quartz.SimpleTriggerBean 每到指定时间则触发一次,在Quartz中对应的调度器为:org.springframework.scheduling.quartz.CronTriggerBean 注:并非每种任务都可以使用这两种触发器,如java.util.TimerTask任务就只能使用第一种。Quartz和spring task都可以支持这两种触发条件。 三、Quartz与Spring的集成 第一种,作业类继承自特定的基类:org.springframework.scheduling.quartz.QuartzJobBean。 第一步:定义作业类 Java代码 import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.springframework.scheduling.quartz.QuartzJobBean; public class Job1 extends QuartzJobBean { private int timeout; private static int i = 0; //调度工厂实例化后,经过timeout时间开始执行调度 public void setTimeout(int timeout) { this.timeout = timeout; } / 要调度的具体任务 / @Override protected void executeInternal(JobExecutionContext context) throws JobExecutionException { System.out.println("定时任务执行中…"); } } 第二步:spring配置文件中配置作业类JobDetailBean Xml代码 <bean name="job1" class="org.springframework.scheduling.quartz.JobDetailBean"> <property name="jobClass" value="com.gy.Job1" /> <property name="jobDataAsMap"> <map> <entry key="timeout" value="0" /> </map> </property> </bean> 说明:org.springframework.scheduling.quartz.JobDetailBean有两个属性,jobClass属性即我们在java代码中定义的任务类,jobDataAsMap属性即该任务类中需要注入的属性值。 第三步:配置作业调度的触发方式(触发器) Quartz的作业触发器有两种,分别是 org.springframework.scheduling.quartz.SimpleTriggerBean org.springframework.scheduling.quartz.CronTriggerBean 第一种SimpleTriggerBean,只支持按照一定频度调用任务,如每隔30分钟运行一次。 配置方式如下: Xml代码 <bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean"> <property name="jobDetail" ref="job1" /> <property name="startDelay" value="0" /><!-- 调度工厂实例化后,经过0秒开始执行调度 --> <property name="repeatInterval" value="2000" /><!-- 每2秒调度一次 --> </bean> 第二种CronTriggerBean,支持到指定时间运行一次,如每天12:00运行一次等。 配置方式如下: Xml代码 <bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean"> <property name="jobDetail" ref="job1" /> <!—每天12:00运行一次 --> <property name="cronExpression" value="0 0 12 ?" /> </bean> 关于cronExpression表达式的语法参见附录。 第四步:配置调度工厂 Xml代码 <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <property name="triggers"> <list> <ref bean="cronTrigger" /> </list> </property> </bean> 说明:该参数指定的就是之前配置的触发器的名字。 第五步:启动你的应用即可,即将工程部署至tomcat或其他容器。 第二种,作业类不继承特定基类。 Spring能够支持这种方式,归功于两个类: org.springframework.scheduling.timer.MethodInvokingTimerTaskFactoryBean org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean 这两个类分别对应spring支持的两种实现任务调度的方式,即前文提到到java自带的timer task方式和Quartz方式。这里我只写MethodInvokingJobDetailFactoryBean的用法,使用该类的好处是,我们的任务类不再需要继承自任何类,而是普通的pojo。 第一步:编写任务类 Java代码 public class Job2 { public void doJob2() { System.out.println("不继承QuartzJobBean方式-调度进行中..."); } } 可以看出,这就是一个普通的类,并且有一个方法。 第二步:配置作业类 Xml代码 <bean id="job2" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"> <property name="targetObject"> <bean class="com.gy.Job2" /> </property> <property name="targetMethod" value="doJob2" /> <property name="concurrent" value="false" /><!-- 作业不并发调度 --> </bean> 说明:这一步是关键步骤,声明一个MethodInvokingJobDetailFactoryBean,有两个关键属性:targetObject指定任务类,targetMethod指定运行的方法。往下的步骤就与方法一相同了,为了完整,同样贴出。 第三步:配置作业调度的触发方式(触发器) Quartz的作业触发器有两种,分别是 org.springframework.scheduling.quartz.SimpleTriggerBean org.springframework.scheduling.quartz.CronTriggerBean 第一种SimpleTriggerBean,只支持按照一定频度调用任务,如每隔30分钟运行一次。 配置方式如下: Xml代码 <bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean"> <property name="jobDetail" ref="job2" /> <property name="startDelay" value="0" /><!-- 调度工厂实例化后,经过0秒开始执行调度 --> <property name="repeatInterval" value="2000" /><!-- 每2秒调度一次 --> </bean> 第二种CronTriggerBean,支持到指定时间运行一次,如每天12:00运行一次等。 配置方式如下: Xml代码 <bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean"> <property name="jobDetail" ref="job2" /> <!—每天12:00运行一次 --> <property name="cronExpression" value="0 0 12 ?" /> </bean> 以上两种调度方式根据实际情况,任选一种即可。 第四步:配置调度工厂 Xml代码 <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <property name="triggers"> <list> <ref bean="cronTrigger" /> </list> </property> </bean> 说明:该参数指定的就是之前配置的触发器的名字。 第五步:启动你的应用即可,即将工程部署至tomcat或其他容器。 到此,spring中Quartz的基本配置就介绍完了,当然了,使用之前,要导入相应的spring的包与Quartz的包,这些就不消多说了。 其实可以看出Quartz的配置看上去还是挺复杂的,没有办法,因为Quartz其实是个重量级的工具,如果我们只是想简单的执行几个简单的定时任务,有没有更简单的工具,有! 四、Spring-Task 上节介绍了在Spring 中使用Quartz,本文介绍Spring3.0以后自主开发的定时任务工具,spring task,可以将它比作一个轻量级的Quartz,而且使用起来很简单,除spring相关的包外不需要额外的包,而且支持注解和配置文件两种 形式,下面将分别介绍这两种方式。 第一种:配置文件方式 第一步:编写作业类 即普通的pojo,如下: Java代码 import org.springframework.stereotype.Service; @Service public class TaskJob { public void job1() { System.out.println(“任务进行中。。。”); } } 第二步:在spring配置文件头中添加命名空间及描述 Xml代码 <beans xmlns="http://www.springframework.org/schema/beans" xmlns:task="http://www.springframework.org/schema/task" 。。。。。。 xsi:schemaLocation="http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd"> 第三步:spring配置文件中设置具体的任务 Xml代码 <task:scheduled-tasks> <task:scheduled ref="taskJob" method="job1" cron="0 ?"/> </task:scheduled-tasks> <context:component-scan base-package=" com.gy.mytask " /> 说明:ref参数指定的即任务类,method指定的即需要运行的方法,cron及cronExpression表达式,具体写法这里不介绍了,详情见上篇文章附录。 <context:component-scan base-package="com.gy.mytask" />这个配置不消多说了,spring扫描注解用的。 到这里配置就完成了,是不是很简单。 第二种:使用注解形式 也许我们不想每写一个任务类还要在xml文件中配置下,我们可以使用注解@Scheduled,我们看看源文件中该注解的定义: Java代码 @Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Scheduled { public abstract String cron(); public abstract long fixedDelay(); public abstract long fixedRate(); } 可以看出该注解有三个方法或者叫参数,分别表示的意思是: cron:指定cron表达式 fixedDelay:官方文档解释:An interval-based trigger where the interval is measured from the completion time of the previous task. The time unit value is measured in milliseconds.即表示从上一个任务完成开始到下一个任务开始的间隔,单位是毫秒。 fixedRate:官方文档解释:An interval-based trigger where the interval is measured from the start time of the previous task. The time unit value is measured in milliseconds.即从上一个任务开始到下一个任务开始的间隔,单位是毫秒。 下面我来配置一下。 第一步:编写pojo Java代码 import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; @Component(“taskJob”) public class TaskJob { @Scheduled(cron = "0 0 3 ?") public void job1() { System.out.println(“任务进行中。。。”); } } 第二步:添加task相关的配置: Xml代码 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:task="http://www.springframework.org/schema/task" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd" default-lazy-init="false"> <context:annotation-config /> <!—spring扫描注解的配置 --> <context:component-scan base-package="com.gy.mytask" /> <!—开启这个配置,spring才能识别@Scheduled注解 --> <task:annotation-driven scheduler="qbScheduler" mode="proxy"/> <task:scheduler id="qbScheduler" pool-size="10"/> 说明:理论上只需要加上<task:annotation-driven />这句配置就可以了,这些参数都不是必须的。 Ok配置完毕,当然spring task还有很多参数,我就不一一解释了,具体参考xsd文档http://www.springframework.org/schema/task/spring-task-3.0.xsd。 附录: cronExpression的配置说明,具体使用以及参数请百度google 字段 允许值 允许的特殊字符 秒 0-59 , - / 分 0-59 , - / 小时 0-23 , - / 日期 1-31 , - ? / L W C 月份 1-12 或者 JAN-DEC , - / 星期 1-7 或者 SUN-SAT , - ? / L C 年(可选) 留空, 1970-2099 , - / - 区间 通配符 ? 你不想设置那个字段 下面只例出几个式子 CRON表达式 含义 "0 0 12 ?" 每天中午十二点触发 "0 15 10 ? " 每天早上10:15触发 "0 15 10 ?" 每天早上10:15触发 "0 15 10 ? " 每天早上10:15触发 "0 15 10 ? 2005" 2005年的每天早上10:15触发 "0 14 ?" 每天从下午2点开始到2点59分每分钟一次触发 "0 0/5 14 ?" 每天从下午2点开始到2:55分结束每5分钟一次触发 "0 0/5 14,18 ?" 每天的下午2点至2:55和6点至6点55分两个时间段内每5分钟一次触发 "0 0-5 14 ?" 每天14:00至14:05每分钟一次触发 "0 10,44 14 ? 3 WED" 三月的每周三的14:10和14:44触发 "0 15 10 ? MON-FRI" 每个周一、周二、周三、周四、周五的10:15触发 Cron 表达式包括以下 7 个字段: 秒 分 小时 月内日期 月 周内日期 年(可选字段) 特殊字符 Cron 触发器利用一系列特殊字符,如下所示: 反斜线(/)字符表示增量值。例如,在秒字段中“5/15”代表从第 5 秒开始,每 15 秒一次。 问号(?)字符和字母 L 字符只有在月内日期和周内日期字段中可用。问号表示这个字段不包含具体值。所以,如果指定月内日期,可以在周内日期字段中插入“?”,表示周内日期值无关紧要。字母 L 字符是 last 的缩写。放在月内日期字段中,表示安排在当月最后一天执行。在周内日期字段中,如果“L”单独存在,就等于“7”,否则代表当月内周内日期的最后一个实例。所以“0L”表示安排在当月的最后一个星期日执行。 在月内日期字段中的字母(W)字符把执行安排在最靠近指定值的工作日。把“1W”放在月内日期字段中,表示把执行安排在当月的第一个工作日内。 井号()字符为给定月份指定具体的工作日实例。把“MON2”放在周内日期字段中,表示把任务安排在当月的第二个星期一。 星号()字符是通配字符,表示该字段可以接受任何可能的值。 字段 允许值 允许的特殊字符 秒 0-59 , - / 分 0-59 , - / 小时 0-23 , - / 日期 1-31 , - ? / L W C 月份 1-12 或者 JAN-DEC , - / 星期 1-7 或者 SUN-SAT , - ? / L C 年(可选) 留空, 1970-2099 , - / 表达式意义 "0 0 12 ?" 每天中午12点触发 "0 15 10 ? " 每天上午10:15触发 "0 15 10 ?" 每天上午10:15触发 "0 15 10 ? " 每天上午10:15触发 "0 15 10 ? 2005" 2005年的每天上午10:15触发 "0 14 ?" 在每天下午2点到下午2:59期间的每1分钟触发 "0 0/5 14 ?" 在每天下午2点到下午2:55期间的每5分钟触发 "0 0/5 14,18 ?" 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发 "0 0-5 14 ?" 在每天下午2点到下午2:05期间的每1分钟触发 "0 10,44 14 ? 3 WED" 每年三月的星期三的下午2:10和2:44触发 "0 15 10 ? MON-FRI" 周一至周五的上午10:15触发 "0 15 10 15 ?" 每月15日上午10:15触发 "0 15 10 L ?" 每月最后一日的上午10:15触发 "0 15 10 ? 6L" 每月的最后一个星期五上午10:15触发 "0 15 10 ? 6L 2002-2005" 2002年至2005年的每月的最后一个星期五上午10:15触发 "0 15 10 ? 63" 每月的第三个星期五上午10:15触发 每天早上6点 0 6 每两个小时 0 /2 晚上11点到早上8点之间每两个小时,早上八点 0 23-7/2,8 每个月的4号和每个礼拜的礼拜一到礼拜三的早上11点 0 11 4 1-3 1月1日早上4点 0 4 1 1 本篇文章为转载内容。原文链接:https://zhanghaiyang.blog.csdn.net/article/details/51397459。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-10-27 18:50:19
344
转载
转载文章
...ERNET 上的电脑访问它的资源的。因而须开启防火墙的这个设置:允许 INTERNET上的机 器访问本机(A电脑)资源。[shutdown –s –t 0]此命令强制关机,一般不要用, WIN7远程连接前几步设置与WinXP一样。 开始→搜索框中输入MSTSC回车→在弹出的对话框中输入需要连接的计算机的IP→连接→账户密码 →确定不久显示器上出现了另一计算机的桌面,远程桌面连接成功。 教你怎样解除电脑开机密码。此方法仅供交流,严禁作为非法手段使用 方法1在开机时按下F8进入带命令提示符的安全模式输入NET USER+用户名+123456/ADD 可把某用户的密码强行设置为123456 方法2如用户忘记登录密码可 按下方法解决 此法不适用于忘记安装时所设定〔administrator〕的密码 1.在计算机启动时按F8及选Safe Mode With Command Prompt 2.选Administrator后便会跳出Command Prompt的窗口 3.用Net的命令增加一个用户,例:增加一个用户名为alanhkg888,命令语法如下: net user alanhkg888/add 4.将新增用户提升至Administrator的权力,例:提升刚才增 加用户alanhkg888的权力,命令语法如下 net localgroup administrators alanhkg888/add 5.完成上列步骤后重新启动计算机,在 启动画面上便增加了一个用户alanhkg888了,选alanhkg888进入www.shanpow.com_删除Download和DataStore文件夹中的所有文件。 6.登入后在控制台→使用者账户→选忘记密码的用户,然后选移除密码 7.在登入画面中选原来的用户便可不需密码情况下等入(因已移除了) 8.删除刚才新增的用户:在控制台→使用者账户→选alanhkg888,然后选移除账户便可 方法3 1、重新启动Windows XP,在启动画面出现后的瞬间按F8,选择带命令行的安全模 式运行。 2、运行过程停止时,系统列出了超级用户administrator和本地用户owner的选择菜单, 点击administrator,进入命令行模式。 3、键入命令:net user owner 123456/add,强制性将owner用户的口令更改为123456。 若想在此添加某一用户:用户名为abcdef,口令为123456的话,请输入net user abcdef 123456/add,添加后可用net localgroup administrators abcdef/add命令将用户提升为 系统管理组administrators用户,具有超级权限。 4.DOS下删windows\system32\config里面的SAM档就可以了 5.开机后按键盘的Delete键进入BIOS界面。找到User Password选项,其默认为关闭状 态。启动并输入用户密码(1~8位英文或数字)。计算机提示请再输入一遍以确认密码无误, 保存退出后重新启动机器,这时就会在开机时出现密码菜单 方法4我们知道在安装Windows XP过程中,首先是以administrator默认登录,然后会要 求创建一个新账户,以便进入Windows XP时使用此新建账户登录,而且在Windows XP的 登录接口中也只会出现创建的这个用户账号,不会出现administrator,但实际上该 administrator账号还是存在的,且密码为空。 【二】:Windows 7实战经验 Windows 7实战经验:完美解决Windows 7更新失败(Windows Update 错误 80070003) 很多用户反映,为什么Windows 7的自动更新会出显未知错误,导致很多更新都不能正确安装?针对这个问题,在我对自己的Windows 7进行更新的时候,有时也会发生类似的问题,经过研究,已经完美解决,下面给大家解决方案! 如果在检查更新时收到Windows Update错误80070003,则需要删除Windows用于标识计算机更新的临时文件。若要删除临时文件,请停止Windows Update服务,删除临时更新文件,重新启动Windows Update服务,然后再次尝试检查Windows更新。 以下步骤为解决Windows 7更新错误方法,本博客亲测有效。 必须以管理员身份进行登录,才能执行这些步骤。 1.单击打开“管理工具(通过单击“开始”按钮,再依次单击“控制面板”,然后单击“管理工具”。 2.双击“服务”。如果系统提示您输入管理员密码或进行确认,请键入该密码或提供确认。 3.单击“名称”列标题以逆序排列名称。找到“Windows Update”服务,右键单击该服务,然后单击“停止”。 1.打开“计算机”。 2.双击安装Windows的本地硬盘(通常是驱动器C)。 3.双击Windows文件夹,然后双击SoftwareDistribution文件夹。 4.双击打开DataStore文件夹,然后删除该文件夹中的所有文件。如果系统提示您输入管理员密码或进行确认,请键入该密码或提供确认。 5.单击“后退”按钮。在SoftwareDistribution文件夹中,双击打开Download文件夹,删除该文件夹中的所有文件,然后关闭窗口。如果系统提示您输入管理员密码或进行确认,请键入该密码或提供确认。 必须以管理员身份进行登录,才能执行这些步骤。 1.单击打开“管理工具(方法同上)”。 2.双击“服务”。如果系统提示您输入管理员密码或进行确认,请键入该密码或提供确认。 3.单击“名称”列标题以逆序排列名称。找到“Windows Update”服务,右键单击该服务,然后单击“启动”。 4.关闭“服务”窗口和“管理工具”窗口。 完成上面操作,你需要重新更新看看可以成功更新了吗,一般因为我们删除了自动更新的一些文件,如果你仔细观察的话,那些文件大小并不是很小,所以我们再更新的时候等待的时间可能会长一些! 【三】:Win10系统提示“无法完成更新正在撤销更改” 更新win10系统补丁之后,系统会提示“window10无法更新,正在撤销”,需要重启好几次,这该怎么办呢?下面小编就向大家介绍一下windows10系统无法完成更新正在撤销更改的解决方法,欢迎大家参考和学习。 系统更新失败,反复重启还是不行,那是不是下载下来的补丁没用了呢??所以我们先要删除Windows更新的缓存文件!在做以下操作之前,首先我们要确认系统内的windows update & BITS服务设置是否开启。 检查方法: 1、按“Win+R”组合键打开运行,输入“services.msc”,点击确定(如果弹出用户账户控制窗口,我们点击“继续”)。 2、双击打开“Background Intelligent Transfer Services”服务。 3、在选项卡点击“常规”,要保证“启动类型”是“自动”或者“手动”。然后点击“服务状态”“启用”按钮。 4. 重复步骤3分别对“Windows Installer”,“Cryptographic Services”, “software licensing service” 以及“Windows Update”这四项服务进行检查。 解决办法: 1、按“Windows+X”打开“命令提示符(管理员)”。 2、输入“net stop wuauserv”回车(我们先把更新服务停止)。 3、输入”%windir%\SoftwareDistribution“回车(删除Download和DataStore文件夹中的所有文件)。 4、最后输入“net start wuauserv”回车(重新开启系统更新服务)。 完成以上的步骤之后,我们就可以在“Windows Update”中再次尝试检查更新即可。 以上就是windows10系统无法完成更新正在撤销更改的解决方法介绍了。遇到同样问题的用户,可以尝试一下这个方法,如果不行,可以留言,小编会继续寻找其他的解决办法。 【四】:Windows更新失败提示错误码80070003怎么办 Windows7,Windows8.1,Windows10在更新过程中,所更新的程序无法安装,导致更新失败,提示错误码80070003。遇到这种情况,无论再试一次,或重启电脑,更新程序仍无法安装,出现错误码80070003提示。关于这个故障,下面小编就为大家介绍一下具体的解决方法吧,欢迎大家参考和学习。 具体解决方法步骤: 1、在电脑更新过程中,更新失败,程序无法安装,出现错误码80070003的提示。如图1 2、打开控制面板,点击“系统和安全”,打开对话框。如图2 3、在打开的对话框中,点击“管理工具”-双击“服务”,在打开的对话框的下方找到“Windows Update"。(如图3),选择Windows Update,点击界面左上角的”停止“按键,或是单击右键选择”停止“。(如图4),以管理员身份进入,如果提示需要输入秘码,则输入秘码。 4、在C盘,打开”Windows"文件夹,-双击打开“SoftwareDistribution"文件夹,找到下面的2个文件夹。打开”DataStore"文件夹,删除里面所有的文件。反回上一步。如图5.1,再打开"Download"文件夹,删除里面所有的文件。(如图5.2) 5、返回第三步的操作,选择Windows Update,右键单击,选择“启动”。 6、做完上面操作后,安装更新文件就会顺利了。 本篇文章为转载内容。原文链接:https://blog.csdn.net/weixin_42620202/article/details/119158423。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-02-16 16:18:33
136
转载
转载文章
...载操作符以及控制属性访问等。 构造器 , 构造器是面向对象编程中的一个重要概念,它指的是当创建一个类的新实例时所调用的方法。在Python中,构造器通常指__init__()方法,该方法在实例化对象时自动执行,用于设置对象的初始状态或执行必要的初始化操作。例如,在上述文章中,类RoundFloatManual中的__init__(self, val)就是构造器,用于接收浮点数并将其四舍五入为两位小数。 重载操作符 , 重载操作符是指在面向对象编程中,重新定义或扩展某个操作符的行为。在Python中,可以通过实现对应的操作符特殊方法来重载操作符,比如实现__add__()方法以改变加法运算符\ +\ 的行为。在示例类Time60中,通过覆盖__add__()方法使得两个时间对象相加时,会返回一个新的Time60对象,其小时和分钟之和为两个输入对象的相应值之和,这就是对加法操作符的重载。
2023-04-19 14:30:42
132
转载
转载文章
...录界面,通过电脑进行访问操作,会员只要平时使用过电脑都能进行访问操作。此系统的开发采用PHP语言开发,基于B/S结构,这些开发环境使系统更加完善。本系统具有易操作、易管理、交互性好的特点,在操作上是非常简单的。因此本系统可以进行开发。 3.1.3 经济可行性 腕表交易系统是基于B/S模式,采用MYSQL数据库储存数据,所要求的硬件和软件环境,市场上都很容易购买,程序开发主要是管理系统的开发和维护。所以程序在开发人力、财力上要求不高,而且此系统不是很复杂,开发周期短,在经济方面具有较高的可行性。 3.1.4 法律可行性 此腕表交易系统是自己设计的管理系统,具有很大的实际意义。开发环境软件和使用的数据库都是开源代码,因此对这个系统进行开发与普通的系统软件设计存在很大不同,没有侵权等问题,在法律上完全具有可行性。 综上所述,腕表交易系统在技术、经济、操作和法律上都具有很高的可行性,开发此程序是很必要的。 3.2 腕表交易系统功能需求分析 此基于SSM的腕表交易系统分前台功能和后台功能: 1)前台部分由用户使用,主要包括用户注册,腕表购物车管理,订单管理,个人资料管理,留言板管理 2)后台部分由管理员使用,主要包括管理员身份验证,商品管理,处理订单,用户信息管理,连接信息管理 3.3 数据库需求分析 数据库的设计通常是以一个已经存在的数据库管理系统为基础的,常用的数据库管理系统有MYSQL,SQL,Oracle等。我采用了Mysql数据库管理系统,建立的数据库名为db_business。 整个系统功能需要以下数据项: 用户:用户id、用户名称、登录密码、用户真实姓名、性别、邮箱地址、联系地址、联系电话、密码问题、答案、注册时间。 留言:主题id、作者姓名、Email、主题名称、留言内容、发布时间。 商品:商品id、名称、价格、图片路径、类型、简要介绍、存储地址、上传人姓名、发布时间、是否推荐。 订单:订单号、用户名、真实姓名、订购日期、Email、地址、邮编、付款方式、联系方式、运送方式、订单核对、其他。 管理员:管理员id、管理员名称、管理员密码。 公告:公告内容、公告时间。 4系统设计 4.1 系统功能模块设计 功能结构图如下: 图9 功能模块设计图 从图中可以看出,网上腕表交易系统可以分为前台和后台两个部分,前台部分由用户使用,主要包括用户注册,生成订单,腕表购物车管理,查看腕表购物车,查看留言,订购产品,订单查询和发布留言7个模块;本文转载自http://www.biyezuopin.vip/onews.asp?id=11975后台部分由管理员使用,主要包括管理员身份验证,商品管理,处理订单,用户信息管理,连接信息管理5个模块。 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html><head><base href="<%=basePath%>"/><title>腕表商城</title><meta http-equiv="pragma" content="no-cache"><meta http-equiv="cache-control" content="no-cache"><meta http-equiv="expires" content="0"> <meta http-equiv="keywords" content="keyword1,keyword2,keyword3"><meta http-equiv="description" content="This is my page"><meta name="viewport" content="width=device-width, initial-scale=1"><!-- Favicon --><link rel="shortcut icon" type="image/x-icon" href="img/favicon.png"><link rel="stylesheet" type="text/css" href="<%=basePath%>home/css/font-awesome.min.css" /><link rel="stylesheet" type="text/css" href="<%=basePath%>home/css/bootstrap.css" /><link rel="stylesheet" type="text/css" href="<%=basePath%>home/css/style.css"><link rel="stylesheet" type="text/css" href="<%=basePath%>home/css/magnific-popup.css"><link rel="stylesheet" type="text/css" href="<%=basePath%>home/css/owl.carousel.css"><script type="text/javascript">function getprofenlei(){ var html = ""; $.ajax({url: "leixing.action?list&page=0&rows=30",type: "POST",async: false, contentType: "application/x-www-form-urlencoded;charset=UTF-8",success: function (data) { $.each(data.rows, function (i, val) { html += ' <li ><a href="home/search.jsp?fenlei='+val.id+'" >'+val.a1+' </a></li>';})} }); $("fenlei").html(html);}function gettop1(){var html = "";$.ajax({url: "leixing.action?list&page=0&rows=10",type: "POST",async: false,success: function (data) {var total='';//<div class="tab-pane active" id="nArrivals">// <div class="nArrivals owl-carousel" id="top1">$.each(data.rows, function (i, valmm) { html+='<div class="nArrivals owl-carousel" id="'+valmm.id+'">';$.ajax({url: "shangpin.action?list&page=0&rows=10",type: "POST",async: false,data: { fenlei:valmm.id },success: function (data) { $.each(data.rows, function (i, val) { html+='<div class="product-grid">'+'<div class="item">'+' <div class="product-thumb">'+' <div class="image product-imageblock"> <a href="home/details.jsp?ids='+val.id+'"> <img data-name="product_image" style="width:223px;height:285px;" src="<%=basePath%>'+val.tupian1+'" alt="iPod Classic" title="iPod Classic" class="img-responsive"> <img style="width:223px;height:285px;" src="<%=basePath%>'+val.tupian1+'" alt="iPod Classic" title="iPod Classic" class="img-responsive"> </a> </div>'+' <div class="caption product-detail text-left">'+' <h6 data-name="product_name" class="product-name mt_20"><a href="home/details.jsp?ids='+val.id+'" title="Casual Shirt With Ruffle Hem">'+val.biaoti+'</a></h6>'+' <div class="rating"> <span class="fa fa-stack"><i class="fa fa-star-o fa-stack-1x"></i><i class="fa fa-star fa-stack-1x"></i></span> <span class="fa fa-stack"><i class="fa fa-star-o fa-stack-1x"></i><i class="fa fa-star fa-stack-1x"></i></span> <span class="fa fa-stack"><i class="fa fa-star-o fa-stack-1x"></i><i class="fa fa-star fa-stack-1x"></i></span> <span class="fa fa-stack"><i class="fa fa-star-o fa-stack-1x"></i><i class="fa fa-star fa-stack-1x"></i></span> <span class="fa fa-stack"><i class="fa fa-star-o fa-stack-1x"></i><i class="fa fa-star fa-stack-x"></i></span> </div>'+'<span class="price"><span class="amount"><span class="currencySymbol">$</span>'+val.jiage+'</span>'+'</span>'+'<div class="button-group text-center">'+' <div class="wishlist"><a href="home/details.jsp?ids='+val.id+'"><span>wishlist</span></a></div>'+'<div class="quickview"><a href="home/details.jsp?ids='+val.id+'"><span>Quick View</span></a></div>'+'<div class="compare"><a href="home/details.jsp?ids='+val.id+'"><span>Compare</span></a></div>'+'<div class="add-to-cart"><a href="home/details.jsp?ids='+val.id+'"><span>Add to cart</span></a></div>'+'</div>'+'</div>'+'</div>'+'</div>'+' </div>'; })html+='</div>'; } })}) $("nArrivals").html(html); } }); 本篇文章为转载内容。原文链接:https://blog.csdn.net/newlw/article/details/127608579。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-03-21 18:24:50
66
转载
转载文章
...2.4.3. 容器外访问和查看 MySQL 日志 docker exec 命令允许您在 Docker 容器内运行命令。 以下命令行将为您提供 mysql 容器内的 bash shell: $ docker exec -it some-mysql bash 第一次启动一个MySQL容器后,需要对账户进行授权,否则无法远程访问,请先使用上面的命令进入容器内,然后使用以下命令连接到mysql服务: mysql -uroot -p 输入密码回车,进入mysql命令界面mysql> 接着授权root远程访问权限: mysql> GRANT ALL PRIVILEGES ON . TO 'root'@'%' IDENTIFIED BY '123456'; 然后就可以远程用MySQL客户端连接到MySQL容器了。 日志可通过 Docker 的容器日志获得: $ docker logs some-mysql 2.4.4. 使用自定义 MySQL 配置文件 MySQL 的默认配置可以在 /etc/mysql/my.cnf 中找到,其中可能包含额外的目录,例如 /etc/mysql/conf.d 或 /etc/mysql/mysql.conf.d。 请检查 mysql 映像本身中的相关文件和目录以获取更多详细信息。 如果 /my/custom/config-file.cnf 是你的自定义配置文件的路径和名称,你可以这样启动你的 mysql 容器(注意这个命令只使用了自定义配置文件的目录路径): $ docker run --name some-mysql -v /my/custom:/etc/mysql/conf.d -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag 这将启动一个新容器 some-mysql,其中 MySQL 实例使用来自 /etc/mysql/my.cnf 和 /etc/mysql/conf.d/config-file.cnf 的组合启动设置,后者的设置优先 . 没有 cnf 文件的配置 许多配置选项可以作为标志传递给 mysqld。 这将使您可以灵活地自定义容器,而无需 cnf 文件。 例如,如果要将所有表的默认编码和排序规则更改为使用 UTF-8 (utf8mb4),只需运行以下命令: $ docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci 如果您想查看可用选项的完整列表,只需运行: $ docker run -it --rm mysql:tag --verbose --help 2.4.5. 环境变量 启动 mysql 镜像时,可以通过在 docker run 命令行中传递一个或多个环境变量来调整 MySQL 实例的配置。 请注意,如果您使用已包含数据库的数据目录启动容器,则以下任何变量都不会产生任何影响:任何预先存在的数据库在容器启动时将始终保持不变。 另请参阅 https://dev.mysql.com/doc/refman/5.7/en/environment-variables.html 以获取 MySQL 的环境变量的文档(尤其是 MYSQL_HOST 等变量,已知与此镜像一起使用时会导致问题)。 MYSQL_ROOT_PASSWORD 此变量是必需的,并指定将为 MySQL root 超级用户帐户设置的密码。 在上面的示例中,它被设置为 my-secret-pw。 MYSQL_DATABASE 此变量是可选的,允许您指定要在映像启动时创建的数据库的名称。 如果提供了用户/密码(见下文),则该用户将被授予对此数据库的超级用户访问权限(对应于 GRANT ALL)。 MYSQL_USER、MYSQL_PASSWORD 这些变量是可选的,用于创建新用户和设置该用户的密码。 该用户将被授予对 MYSQL_DATABASE 变量指定的数据库的超级用户权限(见上文)。 要创建用户,这两个变量都是必需的。 请注意,不需要使用此机制来创建超级用户超级用户,默认情况下会使用 MYSQL_ROOT_PASSWORD 变量指定的密码创建该用户。 MYSQL_ALLOW_EMPTY_PASSWORD 这是一个可选变量。 设置为非空值,例如 yes,以允许使用 root 用户的空白密码启动容器。 注意:除非您真的知道自己在做什么,否则不建议将此变量设置为 yes,因为这将使您的 MySQL 实例完全不受保护,从而允许任何人获得完全的超级用户访问权限。 MYSQL_RANDOM_ROOT_PASSWORD 这是一个可选变量。 设置为非空值,如 yes,为 root 用户生成随机初始密码(使用 pwgen)。 生成的根密码将打印到标准输出(生成的根密码:…)。 MYSQL_ONETIME_PASSWORD 一旦初始化完成,将 root(不是 MYSQL_USER 中指定的用户!)用户设置为过期,强制在第一次登录时更改密码。 任何非空值都将激活此设置。 注意:此功能仅在 MySQL 5.6+ 上受支持。 在 MySQL 5.5 上使用此选项将在初始化期间引发适当的错误。 MYSQL_INITDB_SKIP_TZINFO 默认情况下,入口点脚本会自动加载 CONVERT_TZ() 函数所需的时区数据。 如果不需要,任何非空值都会禁用时区加载。 2.4.6. Docker Secrets 作为通过环境变量传递敏感信息的替代方法,_FILE 可以附加到先前列出的环境变量中,从而导致初始化脚本从容器中存在的文件中加载这些变量的值。 特别是,这可用于从存储在 /run/secrets/<secret_name> 文件中的 Docker 机密中加载密码。 例如: $ docker run --name some-mysql -e MYSQL_ROOT_PASSWORD_FILE=/run/secrets/mysql-root -d mysql:tag 目前,这仅支持 MYSQL_ROOT_PASSWORD、MYSQL_ROOT_HOST、MYSQL_DATABASE、MYSQL_USER和 MYSQL_PASSWORD。 2.4.7. 初始化一个新实例 首次启动容器时,将使用提供的配置变量创建并初始化具有指定名称的新数据库。 此外,它将执行 /docker-entrypoint-initdb.d 中的扩展名为 .sh、.sql 和 .sql.gz 的文件。 文件将按字母顺序执行。 您可以通过将 SQL 转储安装到该目录并提供带有贡献数据的自定义镜像来轻松填充您的 mysql 服务。 SQL 文件将默认导入到 MYSQL_DATABASE 变量指定的数据库中。 2.5. 注意事项 2.5.1. 在哪里存储数据 重要提示:有几种方法可以存储在 Docker 容器中运行的应用程序使用的数据。 我们鼓励 mysql 映像的用户熟悉可用的选项,包括: 让 Docker 通过使用自己的内部卷管理将数据库文件写入主机系统上的磁盘来管理数据库数据的存储。 这是默认设置,对用户来说简单且相当透明。 缺点是对于直接在主机系统(即外部容器)上运行的工具和应用程序,可能很难找到这些文件。 在主机系统(容器外部)上创建一个数据目录,并将其挂载到容器内部可见的目录。 这会将数据库文件放置在主机系统上的已知位置,并使主机系统上的工具和应用程序可以轻松访问这些文件。 缺点是用户需要确保目录存在,例如主机系统上的目录权限和其他安全机制设置正确。 Docker 文档是了解不同存储选项和变体的一个很好的起点,并且有多个博客和论坛帖子在该领域讨论和提供建议。 我们将在这里简单地展示上面后一个选项的基本过程: 在主机系统上的合适卷上创建数据目录,例如 /my/own/datadir。 像这样启动你的 mysql 容器: $ docker run --name some-mysql -v /my/own/datadir:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag 命令的 -v /my/own/datadir:/var/lib/mysql 部分将底层主机系统中的 /my/own/datadir 目录挂载为容器内的 /var/lib/mysql ,默认情况下 MySQL 将 写入其数据文件。 2.5.2. 在 MySQL 初始化完成之前没有连接 如果容器启动时没有初始化数据库,则会创建一个默认数据库。 虽然这是预期的行为,但这意味着在初始化完成之前它不会接受传入的连接。 在使用同时启动多个容器的自动化工具(例如 docker-compose)时,这可能会导致问题。 如果您尝试连接到 MySQL 的应用程序没有处理 MySQL 停机时间或等待 MySQL 正常启动,那么在服务启动之前放置一个连接重试循环可能是必要的。 有关官方图像中此类实现的示例,请参阅 WordPress 或 Bonita。 2.5.3. 针对现有数据库的使用 如果您使用已经包含数据库的数据目录(特别是 mysql 子目录)启动 mysql 容器实例,则应该从运行命令行中省略 $MYSQL_ROOT_PASSWORD 变量; 在任何情况下都将被忽略,并且不会以任何方式更改预先存在的数据库。 2.5.4. 以任意用户身份运行 如果你知道你的目录的权限已经被适当地设置了(例如对一个现有的数据库运行,如上所述)或者你需要使用特定的 UID/GID 运行 mysqld,那么可以使用 --user 调用这个镜像设置为任何值(root/0 除外)以实现所需的访问/配置: $ mkdir data$ ls -lnd datadrwxr-xr-x 2 1000 1000 4096 Aug 27 15:54 data$ docker run -v "$PWD/data":/var/lib/mysql --user 1000:1000 --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag 2.5.5. 创建数据库转储 大多数普通工具都可以工作,尽管在某些情况下它们的使用可能有点复杂,以确保它们可以访问 mysqld 服务器。 确保这一点的一种简单方法是使用 docker exec 并从同一容器运行该工具,类似于以下内容: $ docker exec some-mysql sh -c 'exec mysqldump --all-databases -uroot -p"$MYSQL_ROOT_PASSWORD"' > /some/path/on/your/host/all-databases.sql 2.5.6. 从转储文件恢复数据 用于恢复数据。 您可以使用带有 -i 标志的 docker exec 命令,类似于以下内容: $ docker exec -i some-mysql sh -c 'exec mysql -uroot -p"$MYSQL_ROOT_PASSWORD"' < /some/path/on/your/host/all-databases.sql 备注 docker安装完MySQL,后面就是MySQL容器在跑,基本上就是当MySQL服务去操作,以前MySQL怎么做现在还是一样怎么做,只是个别操作因为docker包了一层,麻烦一点。 有需要的话,我们也可以基于MySQL官方镜像去定制我们自己的镜像,就比如主从镜像之类的。 本篇文章为转载内容。原文链接:https://blog.csdn.net/muluo7fen/article/details/122731852。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-05-29 17:31:06
101
转载
转载文章
...,并提供SQL接口来访问和管理数据。在本文的语境下,Pocket Assistant iOS应用使用SQLite作为聊天记录的本地存储解决方案。 dump命令 , 在SQLite环境中,.dump是一个用于导出整个数据库内容到标准输出(如控制台或重定向到文本文件)的命令。当数据库损坏需要恢复时,可以尝试通过.dump命令将未损坏部分的数据导出为SQL脚本,然后在新的数据库中执行这些脚本来重建数据。 B-tree解析 , B树(B-tree)是一种自平衡的树数据结构,广泛应用于数据库和文件系统中以优化磁盘I/O操作。在SQLite数据库中,B-tree被用来组织和索引表中的数据。文章提及的“B-tree解析”是指当数据库损坏时,通过直接读取和解析SQLite数据库文件中的B-tree结构,绕过SQLite引擎的正常流程,尝试从损坏的数据库中恢复尽可能多的数据。 WAL(Write Ahead Log) , WAL是SQLite数据库实现的一种高级日志机制,全称为“预写日志”。在WAL模式下,SQLite不直接修改主数据库文件,而是在单独的日志文件中记录所有更改,然后异步地将更改合并回主数据库。这提高了并发性能并降低了因意外中断导致数据库损坏的风险。 APM监控平台 , APM(Application Performance Management)即应用程序性能管理,是一种用于监测、诊断和优化软件应用性能的工具集合。在文中提到的APM监控平台,旨在帮助开发者发现和修复可能导致App崩溃的问题,从而降低SQLite数据库由于App异常终止而损坏的可能性。 Backup API , SQLite提供的Backup API是一套用于数据库备份的功能接口,允许在运行时进行热备份,即将一个数据库的全部内容复制到另一个数据库,而无需关闭源数据库或影响其正常的读写操作。在本文背景下,探讨了如何利用Backup API进行SQLite数据库的备份,以及该方法在备份大小和性能上的优缺点。
2023-11-23 18:22:40
127
转载
站内搜索
用于搜索本网站内部文章,支持栏目切换。
知识学习
实践的时候请根据实际情况谨慎操作。
随机学习一条linux命令:
getent passwd username
- 从passwd数据库获取用户信息。
推荐内容
推荐本栏目内的其它文章,看看还有哪些文章让你感兴趣。
2023-04-28
2023-08-09
2023-06-18
2023-04-14
2023-02-18
2023-04-17
2024-01-11
2023-10-03
2023-09-09
2023-06-13
2023-08-07
2023-03-11
历史内容
快速导航到对应月份的历史文章列表。
随便看看
拉到页底了吧,随便看看还有哪些文章你可能感兴趣。
时光飞逝
"流光容易把人抛,红了樱桃,绿了芭蕉。"