前端技术
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
[激光诱导荧光技术 Laser Induc...]的搜索结果
这里是文章列表。热门标签的颜色随机变换,标签颜色没有特殊含义。
点击某个标签可搜索标签相关的文章。
点击某个标签可搜索标签相关的文章。
转载文章
...整数百万种颜色,这项技术对于显示器制造、舞台灯光设计以及印刷行业等领域具有重大意义。 同时,在教育领域,美国麻省理工学院的研究者们正将类似的颜色叠加实验引入到K-12科学课程中,以培养学生的跨学科思维能力,通过动手实验让学生直观理解光学原理,并与数学计算相结合,提升他们解决实际问题的能力。 此外,艺术家和设计师也在利用颜色叠加的原理进行创新实践。例如,荷兰艺术家埃舍尔借助颜色叠加创作出视错觉艺术作品,展示出二维空间内不同颜色相互作用产生的神奇效果。而在时尚界,设计师们通过面料上的颜色叠加与透明度变化,营造出丰富多变且极具层次感的视觉体验。 总的来说,颜色叠加这一基本原理不仅在科普实验中有生动体现,更在科技、教育、艺术等多个领域发挥着重要作用,不断推动着人类对色彩世界的深入理解和广泛利用。
2024-01-20 16:20:26
468
转载
转载文章
...统上,kernelflinger是OS Loader的一个EFI应用 - GRUB镜像组成:GRUB.MBR(boot.img), 硬盘扇区offset 1 到offset 62放置GRUB的core.img,/boot分区的boot/grub/grub.cfg 1.3 创建USB-FDD或者USB-ZIP格式U盘步骤 1)Android上:dd if=/dev/zero of=/dev/block/sda bs=512 count=4 2)Windows上:快速格式化该U盘,这个U盘就只有PBR扇区而没有MBR扇区 2 Windows安装 2.1 BIOS设置 进入BIOS设置,一般有Del、Enter、Esc等键。 2.2 Windows ISO刻录方法 Windows上的Universal USB Installer工具软件:刻录操作系统ISO文件到U盘 Linux下将操作系统ISO文件刻录到U盘:dd if=xxx.iso of=/dev/sda 注意使用的是整个磁盘,所以用的是sda而不是sda1 2.3 Windows GHO镜像安装方法 - 比较常见 1) 制作PE启动U盘 2) 下载Windows ISO镜像后(番茄花园),解压出来,里面包含GHO文件,拷贝到PE启动U盘的GHO文件夹(或者提前将文件.gho拷贝入待装系统的电脑D盘根目录)。 3) 插入PE启动U盘到电脑USB 2.0口,选择从U盘启动,启动到PE界面后,选ghost方式安装,ghost镜像的后缀名.gho。 2.4 Printer 1)HP LaserJet M1005 MFP 2)Nantian PR9 并口-OKI仿真驱动 2.5 Disable Driver Signature bcdedit /set testsigning on bcdedit /set testsigning off 3 Windows网络 3.1 CMD方式配置IP地址 :: netsh: Network Shell @echo off if [%1] == [] ( echo "Usage:" echo "WIN_IP.bat static" echo "WIN_IP.bat dhcp" echo "WIN_IP.bat speed" goto :EOF ) if %1 == static ( call :static_ip ) else if %1 == dhcp ( call :dhcp_ip ) else if %1 == speed ( call :nic_speed ) goto :EOF :: get interface name, use the following command :: getmac /V /FO LIST :static_ip set name="Ethernet" set ip=192.168.0.100 set mask=255.255.255.0 :: gwmetric=1 echo "setting static ip address..." netsh interface ipv4 set address %name% static %ip% %mask% none 1 :: netsh interface ipv4 set dns %name% static 114.114.114.114 :: netsh interface ipv4 add dns %name% 8.8.8.8 goto :EOF :dhcp_ip set name="Ethernet" echo "setting dhcp..." netsh interface ipv4 set address %name% dhcp netsh interface ipv4 set dns %name% dhcp goto :EOF :nic_speed wmic NIC where NetEnabled=true get Name, Speed 3.2 DNS查询流程 1) 现有的DNS缓存 ipconfig /displaydns 2) 查询hosts文件 C:\Windows\System32\drivers\etc\hosts 3) 请求发往DNS服务器 ipconfig /all 3.3 firewall appwiz.cpl msconfig wf.msc Inbound Rules and Outbound Rules Enable 4 File and Printer Sharing (Echo Request - ICMPv4-Out) netsh advfirewall firewall add rule name="UDP ports" protocol=UDP dir=in localport=8080 action=allow https://github.com/DynamoRIO/drmemory/wiki/Downloads 3.4 Multicast - Windows组播client需要使用setsockopt()设置IP_ADD_MEMBERSHIP(加入指定的组播组)才能接收组播server发送的数据。 - 组播MAC地址是指第一个字节的最低位是1的MAC地址。 - 组播MAC地址的前3个字节固定为01:00:5e,后3个字节使用组播IP的后23位。例如239.192.255.251的MAC地址为01:00:5e:40:ff:fb。 - Windows 10 Wireshark要抓取SOME/IP组播报文,需要使用SocketTool工具监听239.192.255.251:30490,然后Wireshark才会显示组播报文,否则不显示(Windows netmon不需要任何设置,就可以抓到全部报文)。 netsh interface ip show joins Win 10 PowerShell: Get-NetAdapter | Format-List -Property ifAlias,PromiscuousMode In Linux, map IP addr to multicast MAC is function ip_eth_mc_map(), kernel eventually calls driver ndo_set_rx_mode() to set multicast MAC to NIC RX MAC filter table. 3.5 NAT 查看当前机器的NAT端口代理表: netsh interface portproxy show all 1) 第三方软件PortTunnel。 2) ICS(Internet Connection Sharing)是NAT的简化版。 3) showcase: USB Reverse Tethering 3.6 route命令用法 route [-f] [-p] [command [destination] [mask netmask] [gateway] [metric metric] [if interface]] route print ::增加一条到192.168.0.10/24网络的路由,网关是192.168.0.1,最后一个if参数是数字,可以使用route print查询,类似于Android的NetId。 route add 192.168.0.0 mask 255.255.255.0 192.168.0.1 metric 1 if 11 ::删除192.168.0.10这条路由 route delete 192.168.0.0 3.7 VLAN PowerShell Get-NetAdapter PowerShell Set-NetAdapterAdvancedProperty -Name \"Ethernet 3\" -DisplayName \"VLAN ID\" -DisplayValue 24 PowerShell Reset-NetAdapterAdvancedProperty -Name \"Ethernet 3\" -DisplayName \"VLAN ID\" 3.8 WiFi AP 1) get password netsh wlan show profiles netsh wlan show profiles name="FAST_ABCD" key=clear 2) enable Soft AP netsh wlan show drivers ::netsh wlan set hostednetwork mode=allow netsh wlan set hostednetwork mode=allow ssid=myWIFI key=12345678 netsh wlan start hostednetwork ::netsh wlan stop hostednetwork 3.9 Malicious software Task Manager Find process name, open file location, remove xxx.exe, rename empty xxx.txt to xxx.exe 4 Office 4.1 Excel Insert Symbol More Symbols Wingdings 2 4.2 Outlook 4.2.1 邮箱清理 点击 自己的邮件名字 Data File Properties(数据文件属性) Folder Size(文件夹大小) Server Data(服务器数据) 从左下角“导航选项”中切换到“日历” View(视图) Change View(更改视图) List(列表) 删除“日历”中过期的项目。 Calendar (Left Bottom) - View (Change View to Calendar) - Choose Menu Month 4.2.2 TCAM filter rule Home - ... - Rules - Create Rule (Manage Rules & Alerts) - Title 4.3 Powerpoint画图 插入 - > 形状 Insert - > Shapes 4.4 Word 升级目录 [References][Update Table] 5 Sprax EA 5.1 Basic Design - Toolbox Message/Argument/Return Value Publish - Save - Save to Clipboard 5.2 Advanced Copy/Paste - Copy to Clipboard - Full Structure for Duplication Copy/Paste - Paste Package from Clipboard 6 USB Win7 CMD: wmic path Win32_PnPSignedDriver | find "Android" wmic path Win32_PnPSignedDriver | find "USB" :: similar to Linux lsusb wmic path Win32_USBControllerDevice get Dependent 7 Abbreviations CAB: Capacity Approval Board NPcap: Nmap Packet Capture wmic: Windows Management Instrumentation Command-line 本篇文章为转载内容。原文链接:https://blog.csdn.net/zoosenpin/article/details/118596813。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-09-10 16:27:10
270
转载
Ruby
...cker”的微虚拟化技术,旨在为无服务器计算提供更高的性能和安全性。这项技术利用轻量级虚拟化容器来运行多个任务,极大地提高了资源利用率。然而,这种高度并发的环境也带来了新的挑战,比如如何确保不同任务之间的数据隔离性和一致性。 在国内,阿里巴巴集团也在积极布局并发编程相关的技术研究。阿里云推出了基于Go语言的高性能微服务框架“MOSN”,该框架支持大规模分布式系统的构建,特别适合处理高并发场景下的请求分发和负载均衡。MOSN的设计理念强调模块化和可扩展性,使得开发者能够轻松应对复杂的业务逻辑。不过,随着越来越多的企业采用类似的架构,如何有效管理线程池大小、避免死锁等问题成为了新的关注焦点。 此外,近期一篇发表在《ACM Transactions on Programming Languages and Systems》上的论文引起了广泛关注。这篇论文探讨了现代编程语言在并发模型设计上的差异,并提出了一种新型的“乐观并发控制”算法。该算法通过预测线程间的冲突概率,动态调整同步策略,从而在一定程度上减少了锁的使用频率。这一方法不仅提升了程序的执行效率,还降低了开发者的维护成本。 从哲学角度来看,无论是技术层面还是理论层面,人类对于并发编程的追求始终未曾停歇。正如古希腊哲学家赫拉克利特所言:“人不能两次踏进同一条河流。”同样,在并发编程的世界里,每一次尝试都是一次全新的探索,而每一次成功都离不开对失败教训的深刻反思。未来,随着量子计算等前沿科技的发展,我们或许将迎来一场关于并发编程范式的革命,而这无疑将为软件工程领域带来前所未有的机遇与挑战。
2025-04-25 16:14:17
32
凌波微步
转载文章
...化进程的加速和互联网技术的发展,大数据已经成为各行各业不可或缺的资源。近年来,国内外许多企业和研究机构不断突破海量数据处理的技术瓶颈,实现了更高效的数据挖掘与分析。 例如,在2022年,Apache Spark社区发布了Spark 3.2版本,进一步优化了其对大规模数据处理的能力,特别是对结构化、半结构化数据的支持更加完善,通过Catalyst优化器的升级以及动态分区剪枝等新特性,有效提升了处理海量数据时的性能表现。 此外,Google公司近期发布的关于Bloom Filter的新研究成果,揭示了一种新型布隆过滤器变体——Counting Bloom Filter with Carry Sketches(CBCS),能够在保持较低错误率的同时,更精准地统计大规模数据集中元素出现的次数,为解决海量数据判重问题提供了新的解决方案。 同时,针对分布式环境下数据存储与计算的需求,Hadoop生态系统的组件如HDFS和YARN也在持续演进中,以适应实时流处理、机器学习等新兴应用场景。而诸如Kafka、Flink等流处理框架的兴起,也为海量数据的实时分析提供了强大支持。 不仅如此,学术界对于Trie树、Bitmap等数据结构的研究也在不断深入,结合新型硬件如SSD、GPU等进行并行优化,使得这些经典数据结构在现代海量数据处理场景下焕发新生。未来,随着量子计算和边缘计算等前沿技术的发展,海量数据处理的方法将更加丰富多元,效率也将有质的飞跃。 综上所述,海量数据处理技术正以前所未有的速度发展和完善,从理论研究到工程实践,各类创新技术和解决方案层出不穷,为大数据时代的数据价值挖掘奠定了坚实基础。广大读者可以通过关注最新的科研成果、行业报告和技术博客,深入了解这一领域的发展趋势和应用案例,以便更好地应对和解决实际工作中的海量数据挑战。
2024-03-01 12:40:17
541
转载
转载文章
...ile) {io_cli();struct Buffer appBuffer = (struct Buffer)memman_alloc(memman, 16);struct TASK task = task_now();task->pTaskBuffer = appBuffer;file_loadfile(file, appBuffer);struct SEGMENT_DESCRIPTOR gdt =(struct SEGMENT_DESCRIPTOR )get_addr_gdt();//select is multiply of 8, divided by 8 get the original valueint code_seg = 21 + (task->sel - first_task_cons_selector) / 8;//change hereint mem_seg = 30 + (task->sel - first_task_cons_selector) / 8;//22;char p = intToHexStr(mem_seg);showString(shtctl, sht_back, 0, show_pos, COL8_FFFFFF, p); show_pos += 16;set_segmdesc(gdt + code_seg, 0xfffff, (int) appBuffer->pBuffer, 0x409a + 0x60);//new memory char q = (char ) memman_alloc_4k(memman, 641024);appBuffer->pDataSeg = (unsigned char)q;set_segmdesc(gdt + mem_seg, 64 1024 - 1,(int) q ,0x4092 + 0x60);task->tss.esp0 = 0;io_sti();start_app(0, code_seg8,641024, mem_seg8, &(task->tss.esp0));io_cli();memman_free_4k(memman,(unsigned int) appBuffer->pBuffer, appBuffer->length);memman_free_4k(memman, (unsigned int) q, 64 1024);memman_free(memman,(unsigned int)appBuffer, 16);task->pTaskBuffer = 0;io_sti();}void console_task(struct SHEET sheet, int memtotal) {....for(;;) { ....else if (i == KEY_RETURN) {....} else if (strcmp(cmdline, "hlt") == 1) {//change herecmd_execute_program("abc.exe");}....}...} 原来的cmd_hlt函数默认加载并执行软盘中的abc.exe程序,现在我们把cmd_hlt改名为cmd_execute_program,并且函数需要传入一个字符串,用于表明要加载执行的程序名字。在该函数的代码实现中,我们使用showString函数把被加载执行的用户进程数据段所对应的全局描述符号给显示到桌面上,上面代码执行后情况如下: 我们看到,在控制台中执行hlt命令后,内核加载了用户进程,同时在控制台下方输出了一个字符串,也就是0x1E,这个数值对应的就是当前运行用户进程其数据段对应的全局描述符号。一旦有这个信息之后,另一个进程就可以有机可乘了。 接着我们在本地目录创建一个新文件叫crack.c,其内容如下: void main() {char p = (char)0x123;p[0] = 'c';p[1] = 'r';p[2] = 'a';p[3] = 'c';p[4] = 'k';p[5] = 0;} 它的目的简单,就是针对内存地址0x123处写入字符串”crack”.接着我们修改一下makefile,使得内核编译时,能把crack.c编译成二进制文件: CFLAGS=-fno-stack-protectorckernel : ckernel_u.asm app_u.asm crack_u.asm cp ckernel_u.asm win_sheet.h win_sheet.c mem_util.h mem_util.c write_vga_desktop.c timer.c timer.h global_define.h global_define.c multi_task.c multi_task.h app_u.asm app.c crack_u.asm crack.c makefile '/media/psf/Home/Documents/操作系统/文档/19/OS-kernel-win-sheet/'ckernel_u.asm : ckernel.o....crack_u.asm : crack.o./objconv -fnasm crack.o crack_u.asmcrack.o : crack.cgcc -m32 -fno-stack-protector -fno-asynchronous-unwind-tables -s -c -o crack.o crack.c 然后我们在本地目录下,把api_call.asm拷贝一份,并命名为crack_call.asm,后者内容与前者完全相同,只不过稍微有那么一点点改变,例如: BITS 32mov AX, 30 8mov DS, axcall mainmov edx, 4 ;返回内核int 02Dh.... 这里需要注意,语句: mov AX, 30 8mov DS, ax 其中30对应的就是前面显示的0x1E,这两句汇编的作用是,把程序crack的数据段设置成下标为30的全局描述符所指向的内存段一致。这就意味着crack进程所使用的数据段就跟hlt启动的进程所使用的数据段一致了!于是在crack.c中,它对内存地址为0x123的地方写入字符串”crack”,那就意味着对hlt加载用户进程的内存空间写入对应字符串! 完成上面代码后,我们在java项目中,增加代码,一是用来编译crack进程,而是把crack代码写入虚拟磁盘。在OperatingSystem.java中,将代码做如下添加: public void makeFllopy() {writeFileToFloppy("kernel.bat", false, 1, 1);....header = new FileHeader();header.setFileName("crack");header.setFileExt("exe");file = new File("crack.bat");in = null;try {in = new FileInputStream(file);long len = file.length();int count = 0;while (count < file.length()) {bbuf[count] = (byte) in.read();count++;}in.close();}catch(IOException e) {e.printStackTrace();return;}header.setFileContent(bbuf);fileSys.addHeader(header);....}public static void main(String[] args) {CKernelAsmPrecessor kernelPrecessor = new CKernelAsmPrecessor();kernelPrecessor.process();kernelPrecessor.createKernelBinary();CKernelAsmPrecessor appPrecessor = new CKernelAsmPrecessor("hlt.bat", "app_u.asm", "app.asm", "api_call.asm");appPrecessor.process();appPrecessor.createKernelBinary();CKernelAsmPrecessor crackPrecessor = new CKernelAsmPrecessor("crack.bat", "crack_u.asm", "crack.asm", "crack_call.asm");crackPrecessor.process();crackPrecessor.createKernelBinary();OperatingSystem op = new OperatingSystem("boot.bat");op.makeFllopy();} 在main函数中,我们把crack.c及其附属汇编文件结合在一起,编译成二进制文件crack.bat,在makeFllopy中,我们把编译后的crack.bat二进制数据读入,并把它写入到虚拟磁盘中,当系统运行起来后,可以把crack.bat二进制内容作为进程加载执行。 完成上面代码后,回到内核的C语言部分,也就是write_vga_desktop.c做一些修改,在kernel_api函数中,修改如下: int kernel_api(int edi, int esi, int ebp, int esp,int ebx, int edx, int ecx, int eax) {....else if (edx == 14) {sheet_free(shtctl, (struct SHEET)ebx);//change herecons_putstr((char)(task->pTaskBuffer->pDataSeg + 0x123));}....}void console_task(struct SHEET sheet, int memtotal) {....for(;;) {....else if (i == KEY_RETURN) {....else if (strcmp(cmdline, "crack") == 1) {cmd_execute_program("crack.exe");}....}....} 在kernel_api中,if(edx == 14)对应的api调用是api_closewin,也就是当用户进程关闭窗口时,我们把进程数据偏移0x123处的数据当做字符串打印到控制台窗口上,在console_task控制台进程主函数中,我们增加了对命令crack的响应,当用户在控制台上输入命令”crack”时,将crack代码加载到内核中运行。上面代码完成后,编译内核,然后用虚拟机将内核加载,系统启动后,我们现在一个控制台中输入hlt,先启动用户进程。然后点击”shift + w”,启动另一个控制台窗口,在其中输入crack,运行crack程序: 接着把点击tab键,把焦点恢复到窗口task_a,然后用鼠标点击运行hlt命令的窗口,把输入焦点切换到该控制台,然后再次点击tab键,把执行权限提交给运行hlt命令的控制台,此时点击回车,介绍用户进程启动的窗口,结果情况如下: 此时我们可以看到,运行hlt命令,执行用户进程的控制台窗口居然输出了字符串”crack”,而这个字符串正是crack.c在执行时,写入地址0x123的字符串。这就意味着一个恶意进程成功修改了另一个进程的内存数据,也相当于一个流氓程序把一只咸猪手伸到其他用户进程的裙底,蹂躏一番后留下了猥琐的证据。 那么如何防范恶意进程对其他程序的非法入侵呢,这就得使用CPU提供的LDT机制,也就是局部描述符表,该机制的使用,我们将在下一节详细讲解。更详细的讲解和代码演示调试,请参看视频: 更详细的讲解和代码调试演示过程,请参看视频 Linux kernel Hacker, 从零构建自己的内核 更多技术信息,包括操作系统,编译器,面试算法,机器学习,人工智能,请关照我的公众号: 本篇文章为转载内容。原文链接:https://blog.csdn.net/tyler_download/article/details/78731905。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-03-14 19:08:07
254
转载
Spark
...括JavaSerializer、KryoSerializer等。不同的序列化方式会影响数据的大小以及读取效率。 我曾经试过直接使用默认的JavaSerializer,结果发现性能非常差。后来改用了KryoSerializer之后,才明显感觉到速度有所提升。话说回来啊,用 KryoSerializer 的时候可别忘了先给所有要序列化的类都注册好,不然程序很可能就“翻车”报错啦! java import org.apache.spark.serializer.KryoRegistrator; import com.esotericsoftware.kryo.Kryo; public class MyRegistrator implements KryoRegistrator { @Override public void registerClasses(Kryo kryo) { kryo.register(MyClass.class); // 注册其他需要序列化的类... } } 然后在SparkConf中设置: java SparkConf conf = new SparkConf(); conf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer"); conf.set("spark.kryo.registrator", "MyRegistrator"); 2.3 缓存时机的选择失误 还有一个关键点在于缓存的时机。有些人一启动任务就赶紧给数据加上.cache(),觉得这样数据就能一直乖乖待在内存里,不用再费劲去读了。但实际上,这种做法并不总是最优解。 比如,在某些情况下,数据可能只会在特定阶段被频繁访问,而在其他阶段则很少用到。要是你提前把这部分数据缓存了,不光白白占用了宝贵的内存空间,搞不好后面真要用缓存的地方还找不到足够的空位呢! 因此,合理规划缓存策略非常重要。比如说,在某个任务快开始了,你再随手调用一下.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
81
素颜如水
转载文章
...介绍一下 tvm 在linux环境下的安装与编译,以及如何使用vscode来配置tvm的远程连接调试环境。 所需软硬件环境: 环境 版本 local system windows 10 service system ubuntu 18.04 tvm latest(0.9.dev0) python(conda) python 3.8.13 local IDE vscode 1. 安装TVM 1.1 下载源码 从github上拉取源码git clone --recursive https://github.com/apache/tvm tvm --recursive指令:由于tvm依赖了很多第三方的开源库(子模块) 加入该参数之后也将相应的子模块一起进行clone 或者直接下载源码https://tvm.apache.org/download 1.2 创建虚拟环境及安装依赖库 使用conda创建tvm的虚拟python环境,python版本为3.8,虚拟环境名为tvmenv: conda create -n tvmenv python=3.8 编辑tvm目录下的conda/build-environment.yaml文件: conda/build-environment.yaml Build environment that can be used to build tvm.name: tvmenv The conda channels to lookup the dependencieschannels:- anaconda- conda-forge 将name的值改为刚刚创建的虚拟环境名tvmenv 执行下面的指令,将构建tvm所需的环境依赖更新到当前虚拟环境中: conda env update -f conda/build-environment.yaml conda env update -n tvmenv -f conda/build-environment.yaml 设置完之后需要重新deactivate/activate对环境进行激活 如果上述命令执行较慢,可以将conda换成国内源(建议使用北京外国语大学的开源镜像站):参考连接 然后修改conda/build-environment.yaml文件: channels:- defaults - anaconda - conda-forge 安装python依赖库: pip install decorator tornado psutil 'xgboost<1.6.0' cloudpickle -i https://pypi.tuna.tsinghua.edu.cn/simple 如果使用onnx或者pytorch作为原始模型,则还需要安装相应的依赖库pip install onnx onnxruntime -i https://pypi.tuna.tsinghua.edu.cn/simplepip install torch==1.7.1 torchvision==0.8.2 torchaudio==0.7.2 -i https://pypi.tuna.tsinghua.edu.cn/simple 在当前虚拟环境中添加用于tvm debug的环境变量: conda env config vars set TVM_LOG_DEBUG="ir/transform.cc=1,relay/ir/transform.cc=1" conda env config vars set TVM_LOG_DEBUG="ir/transform.cc=1,relay/ir/transform.cc=1" -n tvmenv 设置完之后需要重新deactivate/activate对环境进行激活是环境变量生效 使用这种方式设置环境变量的好处是:只有当前环境被激活(conda activate)时,自定义设置的环境变量才起作用,当conda deactivate后自定义的环境变量会自动清除。 当然,也可以更简单粗暴一些: export TVM_LOG_DEBUG="ir/transform.cc=1,relay/ir/transform.cc=1" 在当前虚拟环境中添加用于tvm python的环境变量: export TVM_HOME=your tvm pathexport PYTHONPATH=$TVM_HOME/python:${PYTHONPATH} 1.3 编译TVM源码 如果linux上没有安装C/C++的编译环境,需要进行安装: 更新软件apt-get update 安装apt-get install build-essential 安装cmakeapt-get install cmake 在tvm目录下创建build文件夹,并将cmake/config.cmake文件复制到此文件夹中: mkdir buildcp cmake/config.cmake build/ 编辑build/config.cmake进行相关配置: 本次是在cpu上进行测试,因此没有配置cudaset(USE_LLVM ON) line 136set(USE_RELAY_DEBUG ON) line 285(建议先 OFF) 在末尾添加一个cmake的编译宏,确保编译出来的是debug版本set(CMAKE_BUILD_TYPE Debug) 编译tvm,这里开启了16个线程: cd buildcmake ..make -j 16 建议开多个线程,否则编译速度很慢哦 大约5分钟,即可生成我们需要的两个共享链接库:libtvm.so 和 libtvm_runtime.so 1.4 验证安装是否成功 tvm版本验证: import tvmprint(tvm.__version__) pytorch模型验证: from_pytorch.py https://tvm.apache.org/docs/how_to/compile_models/from_pytorch.html ps: TVM supports PyTorch 1.7 and 1.4. Other versions may be unstable.import tvmfrom tvm import relayfrom tvm.contrib.download import download_testdataimport numpy as np PyTorch importsimport torchimport torchvision Load a pretrained PyTorch model -------------------------------model_name = "resnet18"model = getattr(torchvision.models, model_name)(pretrained=True) or model = torchvision.models.resnet18(pretrained=True) or pth_file = 'resnet18-f37072fd.pth' model = torchvision.models.resnet18() ckpt = torch.load(pth_file) model.load_state_dict(ckpt)model = model.eval() We grab the TorchScripted model via tracinginput_shape = [1, 3, 224, 224]input_data = torch.randn(input_shape)scripted_model = torch.jit.trace(model, input_data).eval() Load a test image ----------------- Classic cat example!from PIL import Image img_url = "https://github.com/dmlc/mxnet.js/blob/main/data/cat.png?raw=true" img_path = download_testdata(img_url, "cat.png", module="data")img_path = 'cat.png'img = Image.open(img_path).resize((224, 224)) Preprocess the image and convert to tensorfrom torchvision import transformsmy_preprocess = transforms.Compose([transforms.Resize(256),transforms.CenterCrop(224),transforms.ToTensor(),transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),])img = my_preprocess(img)img = np.expand_dims(img, 0) Import the graph to Relay ------------------------- Convert PyTorch graph to Relay graph. The input name can be arbitrary.input_name = "input0"shape_list = [(input_name, img.shape)]mod, params = relay.frontend.from_pytorch(scripted_model, shape_list) Relay Build ----------- Compile the graph to llvm target with given input specification.target = tvm.target.Target("llvm", host="llvm")dev = tvm.cpu(0)with tvm.transform.PassContext(opt_level=3):lib = relay.build(mod, target=target, params=params) Execute the portable graph on TVM --------------------------------- Now we can try deploying the compiled model on target.from tvm.contrib import graph_executordtype = "float32"m = graph_executor.GraphModule(lib["default"](dev)) Set inputsm.set_input(input_name, tvm.nd.array(img.astype(dtype))) Executem.run() Get outputstvm_output = m.get_output(0) Look up synset name ------------------- Look up prediction top 1 index in 1000 class synset. synset_url = "".join( [ "https://raw.githubusercontent.com/Cadene/", "pretrained-models.pytorch/master/data/", "imagenet_synsets.txt", ] ) synset_name = "imagenet_synsets.txt" synset_path = download_testdata(synset_url, synset_name, module="data") https://raw.githubusercontent.com/Cadene/pretrained-models.pytorch/master/data/imagenet_synsets.txtsynset_path = 'imagenet_synsets.txt'with open(synset_path) as f:synsets = f.readlines()synsets = [x.strip() for x in synsets]splits = [line.split(" ") for line in synsets]key_to_classname = {spl[0]: " ".join(spl[1:]) for spl in splits} class_url = "".join( [ "https://raw.githubusercontent.com/Cadene/", "pretrained-models.pytorch/master/data/", "imagenet_classes.txt", ] ) class_name = "imagenet_classes.txt" class_path = download_testdata(class_url, class_name, module="data") https://raw.githubusercontent.com/Cadene/pretrained-models.pytorch/master/data/imagenet_classes.txtclass_path = 'imagenet_classes.txt'with open(class_path) as f:class_id_to_key = f.readlines()class_id_to_key = [x.strip() for x in class_id_to_key] Get top-1 result for TVMtop1_tvm = np.argmax(tvm_output.numpy()[0])tvm_class_key = class_id_to_key[top1_tvm] Convert input to PyTorch variable and get PyTorch result for comparisonwith torch.no_grad():torch_img = torch.from_numpy(img)output = model(torch_img) Get top-1 result for PyTorchtop1_torch = np.argmax(output.numpy())torch_class_key = class_id_to_key[top1_torch]print("Relay top-1 id: {}, class name: {}".format(top1_tvm, key_to_classname[tvm_class_key]))print("Torch top-1 id: {}, class name: {}".format(top1_torch, key_to_classname[torch_class_key])) 2. 配置vscode 安装两个vscode远程连接所需的两个插件,具体如下图所示: 安装完成之后,在左侧工具栏会出现一个图标,点击图标进行ssh配置: ssh yourname@yourip -A 然后右键选择在当前窗口进行连接: 除此之外,还可以设置免费登录,具体可参考这篇文章。 当然,也可以使用windows本地的WSL2,vscode连接WSL还需要安装WSL和Dev Containers这两个插件。 在服务器端执行code .会自动安装vscode server,安装位置在用户的根目录下: 3. 安装FFI Navigator 由于TVM是由Python和C++混合开发,且大多数的IDE仅支持在同一种语言中查找函数定义,因此对于跨语言的FFI 调用,即Python跳转到C++或者C++跳转到Python,vscode是做不到的。虽然解决这个问题在技术上可能非常具有挑战性,但我们可以通过构建一个与FFI注册码模式匹配并恢复必要信息的项目特定分析器来解决这个问题,FFI Navigator就这样诞生了,作者仍然是陈天奇博士。 安装方式如下: 建议使用源码安装git clone https://github.com/tqchen/ffi-navigator.git 安装python依赖cd ffi-navigator/pythonpython setyp.py install vscode需要安装FFI Navigator插件,直接搜索安装即可(安装到服务器端)。 最后需要在.vscode/setting.json进行配置,内容如下: {"python.analysis.extraPaths": ["${workspaceFolder}/python"], // 添加额外导入路径, 告诉pylance自定义的python库在哪里"ffi_navigator.pythonpath": "/home/liyanpeng/anaconda3/envs/tvmenv/bin/python", // 配置FFI Navigator"python.defaultInterpreterPath": "/home/liyanpeng/anaconda3/envs/tvmenv/bin/python","files.associations": {"type_traits": "cpp","fstream": "cpp","thread": "cpp",".tcc": "cpp"} } 更详细内容可以参考项目链接。 结束语 对于vscode的使用技巧及C/C++相关的配置,这里不再详细的介绍了,感兴趣的小伙伴们可以了解下。 本篇文章为转载内容。原文链接:https://blog.csdn.net/qq_42730750/article/details/126723224。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-12-12 20:04:26
87
转载
转载文章
... (ps:在我们使用Linux系统时,除了用户创建的账号之外,还会产生系统或程序安装过程中产生的许多其他账号,除了超级用户root外,其他账号都是用来维护系统运作的,一般不允许登录,常见的非登录用户有bin、adm、mail、lp、nobody、ftp等。) 格式:usermod -s /sbin/nologin 用户名 2锁定长期不使用的账号: [root@hehe ~] usermod -L test2 锁定用户账号方法一[root@hehe ~] passwd -l test3 锁定用户账号方法二[root@hehe ~] usermod -U test2 解锁用户账号方法一[root@hehe~] passwd -u test3 解锁用户账号方法二查看账户有没有被锁:passwd -S [用户名] 3.删除无用的账号 [root@hehe ~] userdel test1[root@hehe~] userdel -r test2 4.锁定账号文件passwd,shadow [root@hehe ~] chattr +i /etc/passwd /etc/shadow 锁定文件,包括root也无法修改[root@hehe ~] chattr -i /etc/passwd /etc/shadow 解锁文件[root@hehe ~] lsattr /etc/passwd /etc/shadow查看文件状态属性 举个例子: 二.密码安全控制: 1.设置密码有效期: 1.[root@localhost ~] chage -M 60 test3 这种方法适合修改已经存在的用户12.[root@localhost ~] vim /etc/login.defs 这种适合以后添加新用户PASS_MAX_DAYS 30 1.这个方法适用于早就已经存在的用户: 2.这个方法适用于新用户 2.要求用户下次登录时改密码: [root@hehe ~] chage -d 0 [用户名] 强制要求用户下次登陆时修改密码 三.命令历史限制与自动注销 1.命令历史限制: 1.减少记录的命令条数 减少记录命令的条数:1.[root@hehe ~] vim /etc/profile 进入配置文件修改限制命令条数。适合新用户HISTSIZE=200 修改限制命令为200条,系统默认是1000条profile [root@lhehe ~] source /etc/ 刷新配置文件,使文件立即生效2.[root@hehe~] export HISTSIZE=200 适用于当前(之后)用户[root@hehe~] source /etc/profile [root@hehe ~] source /etc/profile 刷新配置文件,使文件立即生效 1.减少记录命令的条数(适用之前的用户): 2.注销时自动清空命令历史 3. 注销时自动清空命令:[root@hehe ~] vim ~/.bash_logout(临时清除,重启缓存的话还在)echo "" > ~/.bash_history(永久删除)history是查你使用过的命令 2.终端自动注销: 1.闲置600秒后自动注销 闲置600秒后自动注销:[root@hehe ~]vim .bash_profile 进入配置文件export TMOUT=600 全局声明超过60秒闲置后自动注销终端[root@hehe ~] source .bash_profile [root@hehe ~] echo $TMOUT[root@hehe ~] export TMOUT=600 如果不在配置文件输入这条命令,那么是对当前用户生效[root@hehe ~]vim .bash_profile export TMOUT=600 注释掉这条命令,就不会自动注销了 四.PAM安全认证 1.su的命令的安全隐患 1.,默认情况下,任何用户都允许使用su命令,有机会反复尝试其他用户(如root) 的登录密码,带来安全风险; 2.为了加强su命令的使用控制,可借助于PAM认证模块,只允许极个别用户使用su命令进行切换。 2.什么是PAM 1.PAM(Pluggable Authentication Modules)可插拔式认证模块 2.是一种高效而且灵活便利的用户级别的认证方式; 3.也是当前Linux服务器普遍使用的认证方式。 4.PAM提供了对所有服务进行认证的中央机制,适用于login,远程登陆,su等应用 5.系统管理员通过PAM配置文件来制定不同的应用程序的不同认证策略 3.PAM认证原理 1.PAM认证一般遵循的顺序: Service (服务) --> PAM (配置文件) --> pam_.so;, 2.PAM认证首先要确定哪一项应用服务,然后加载相应的PAM的配置文件(位于/etc/pam.d下),最后调用认 模块(位于/lib64/security/下)进行安全认证。 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
95
转载
转载文章
...以用sklearn中linear_model模块来进行建模 from sklearn.preprocessing import StandardScaler sklearn进行线性回归前必须要进行标准化from sklearn.linear_model import LassoCV Lasso的交叉验证方法con_xcols = ['Age', 'Income', 'dist_home_val', 'dist_avg_income']scaler = StandardScaler()X = scaler.fit_transform(raw_1[con_xcols])y = raw_1['avg_exp_ln']lasso_alphas = np.logspace(-3, 0, 100, base = 10)lcv = LassoCV(alphas = lasso_alphas, cv = 10)lcv.fit(X, y)print('best alpha %.4f' % lcv.alpha_)print('the r-square %.4f' % lcv.score(X, y)) 接下来画出不同alpha下的岭迹图,来看alpha值对系数的影响 from sklearn.linear_model import Lassocoefs = []lasso = Lasso()for i in lasso_alphas:lasso.set_params(alpha = i)lasso.fit(X, y)coefs.append(lasso.coef_)ax = plt.gca()ax.plot(lasso_alphas, coefs)ax.set_xscale('log')ax.set_xlabel('$\\alpha$')ax.set_ylabel('coefs value') 从图中可以看到随着alpha的增大,系数不断在减小,有些系数会优先收缩为0,再继续增大时所欲系数都会为0,通过该特性从而达到变量筛选的目的。将LassoCV得到的系数打印出来,可以看到用户月均信用卡支出和当地小区均价、当地人均收入成正比,当地人均收入水平的影响更大。 以上就是线形回归在应用时的注意事项。 本篇文章为转载内容。原文链接:https://blog.csdn.net/baidu_26137595/article/details/123766191。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-11-23 15:52:56
106
转载
Kafka
...2181 --replication-factor 1 --partitions 3 --topic user_events 这里的关键点在于,主题的名字要尽量简单明了,避免使用特殊字符或者空格。哎呀,这就好比你给文件夹起个特别绕口的名字,结果自己都记不住路径了,Kafka也是一样!它会根据主题的名字创建对应的文件夹结构,但要是主题名太复杂,搞不好就会在找东西的时候迷路,路径解析起来就容易出岔子啦。而且啊,主题的名字最好起得通俗易懂一点,让大伙儿一眼扫过去就明白这是干啥用的。 2.2 分区(Partition):主题的分身术 接着说分区(Partition)。每个主题都可以被划分为多个分区,每个分区就是一个日志文件。分区的作用是什么呢?它可以提高并发性和扩展性。比如说,你有个主题叫orders(订单),你可以把它分成5个区(分区)。这样一来,不同的小伙伴就能一起开工,各自处理这些区里的数据啦! java // 查看主题的分区信息 kafka-topics.sh --describe --zookeeper localhost:2181 --topic orders 分区的数量决定了并发的上限。所以,在设计主题时,你需要仔细权衡分区数量。太多的话,管理起来麻烦;太少的话,可能无法充分利用资源。我一般会根据预计的消息量来决定分区的数量。比如说,如果一秒能收到几千条消息,那分区设成10到20个就挺合适的。毕竟分区太多太少了都不好,得根据实际情况来调,不然可能会卡壳或者资源浪费啊! 2.3 消费者组(Consumer Group):团队协作的秘密武器 最后,我们来说消费者组(Consumer Group)。消费者组是一组消费者的集合,它们共同消费同一个主题的消息。每个消费者组都有一个唯一的名称,这个名字同样非常重要。 java // 创建一个消费者组 kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic user_events --group my_consumer_group 消费者组的设计理念是为了实现负载均衡和故障恢复。比如说,如果有两个小伙伴在一个小组里,系统就会帮他们自动分配任务(也就是主题的分区),这样大家就不会抢来抢去,重复干同样的活儿啦!而且呢,要是有个消费者挂掉了或者出问题了,其他的消费者就会顶上来,接手它负责的那些分区,接着干活儿,完全不受影响。 --- 3. 组织结构 Kafka的大脑与四肢 3.1 集群(Cluster):Kafka的心脏 Kafka集群是由多个Broker组成的,Broker是Kafka的核心组件,负责存储和转发消息。一个Broker就是一个节点,多个Broker协同工作,形成一个分布式的系统。 java // 启动Kafka Broker nohup kafka-server-start.sh config/server.properties & Broker的数量决定了系统的容错能力和性能。其实啊,通常咱们都会建议弄三个Broker,为啥呢?就怕万一有个家伙“罢工”了,比如突然挂掉或者出问题,别的还能顶上,整个系统就不耽误干活啦!不过,Broker的数量也不能太多,否则会增加管理和维护的成本。 3.2 Zookeeper:Kafka的大脑 Zookeeper是Kafka的协调器,它负责管理集群的状态和配置。没有Zookeeper,Kafka就无法正常运作。比如说啊,新添了个Broker(也就是那个消息中转站),Zookeeper就会赶紧告诉其他Broker:“嘿,快看看这位新伙伴,更新一下你们的状态吧!”还有呢,要是某个分区的老大换了(Leader切换了),Zookeeper也会在一旁默默记好这笔账,生怕漏掉啥重要信息似的。 java // 启动Zookeeper nohup zookeeper-server-start.sh config/zookeeper.properties & 虽然Zookeeper很重要,但它也有一定的局限性。比如,它可能会成为单点故障,影响整个系统的稳定性。因此,近年来Kafka也在尝试去掉对Zookeeper的依赖,开发了自己的内部协调机制。 3.3 日志(Log):Kafka的四肢 日志是Kafka存储消息的地方,每个分区对应一个日志文件。嘿,这个日志设计可太聪明了!它用的是顺序写入的方法,就像一条直线往前跑,根本不用左顾右盼,写起来那叫一个快,效率直接拉满! java // 查看日志路径 cat config/server.properties | grep log.dirs 日志的大小可以通过参数log.segment.bytes来控制。默认值是1GB,你可以根据实际情况调整。要是日志文件太大了,查个东西就像在大海捞针一样慢吞吞的;但要是弄得太小吧,又老得换新的日志文件,麻烦得很,还费劲。 --- 4. 实战演练 从零搭建一个Kafka环境 说了这么多理论,咱们来实际操作一下吧!假设我们要搭建一个简单的Kafka环境,用来收集用户的登录日志。 4.1 安装Kafka和Zookeeper 首先,我们需要安装Kafka和Zookeeper。可以从官网下载最新的二进制包,解压后按照文档配置即可。 bash 下载Kafka wget https://downloads.apache.org/kafka/3.4.0/kafka_2.13-3.4.0.tgz 解压 tar -xzf kafka_2.13-3.4.0.tgz 4.2 创建主题和消费者 接下来,我们创建一个名为login_logs的主题,并启动一个消费者来监听消息。 bash 创建主题 bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 3 --topic login_logs 启动消费者 bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic login_logs --from-beginning 4.3 生产消息 最后,我们可以编写一个简单的Java程序来生产消息。 java import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.ProducerRecord; import java.util.Properties; public class KafkaProducerExample { public static void main(String[] args) { Properties props = new Properties(); props.put("bootstrap.servers", "localhost:9092"); props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer"); KafkaProducer producer = new KafkaProducer<>(props); for (int i = 0; i < 10; i++) { producer.send(new ProducerRecord<>("login_logs", "key" + i, "value" + i)); } producer.close(); } } 这段代码会向login_logs主题发送10条消息,每条消息都有一个唯一的键和值。 --- 5. 总结 Kafka的魅力在于细节 好了,到这里咱们的Kafka之旅就告一段落了。通过这篇文章,我希望大家能更好地理解Kafka的命名规范和组织结构。Kafka为啥这么牛?因为它在设计的时候真是把每个小细节都琢磨得特别透。就像给主题起名字吧,分个区啦,还有消费者组怎么配合干活儿,这些地方都能看出人家确实是下了一番功夫的,真不是随便凑合出来的! 当然,Kafka的学习之路还有很多内容需要探索,比如监控、调优、安全等等。其实我觉得啊,只要你把命名的规矩弄明白了,东西该怎么放也心里有数了,那你就算是走上正轨啦,成功嘛,它就已经在向你招手啦!加油吧,朋友们! --- 希望这篇文章对你有所帮助,如果有任何疑问,欢迎随时交流哦!
2025-04-05 15:38:52
95
彩虹之上
转载文章
...Table: 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
转载
转载文章
...ableScheduling注解开启定时任务功能,并利用@Scheduled注解直接在服务类的方法上定义执行计划。这种方式不仅简化了配置流程,而且增强了代码的可读性和维护性。 另外,针对大规模分布式系统中的定时任务调度,Apache Eagle和Apollo等开源项目也提供了强大的解决方案。例如,Apollo拥有丰富的定时任务调度策略以及灵活的分片、依赖处理机制,能够有效应对高并发场景下的定时任务管理需求。 与此同时,云原生环境下的Kubernetes CronJob也是一个值得关注的方向。CronJob作为Kubernetes的一部分,可以根据Cron表达式在集群中调度容器化的定时任务,实现了与容器编排平台的高度集成。 此外,在深入研究定时任务原理时,可以追溯到操作系统级别的定时器和调度算法,如Linux系统的timerfd和POSIX信号定时器机制,这些底层技术为上层应用提供精确且高效的定时服务。 总之,随着技术的演进与发展,Java定时任务的实现方式日趋丰富多样,开发者应根据实际应用场景选择最适合的技术方案,同时关注社区前沿动态,以确保所采用的定时任务技术始终与时俱进。
2023-10-27 18:50:19
344
转载
转载文章
...全控制另一台计算机的技术。文中提到的远程登录操作,就是在A电脑上通过开启远程桌面服务,并获取其公网IP地址后,在B电脑上输入该IP地址进行连接,实现远程查看和操作A电脑桌面的功能。这一技术广泛应用于远程办公、技术支持、服务器管理等领域。 QoS数据计划程序(Quality of Service Data Plan Policy) , QoS数据计划程序是Windows操作系统中的一种网络服务质量保证机制,它可以根据不同的网络流量需求为其分配优先级,确保关键业务或应用程序有足够的带宽资源。在本文中,通过组策略编辑器调整QoS数据计划程序,可以限制某些进程占用的网络带宽,从而优化整体网络使用情况。 SoftwareDistribution文件夹 , SoftwareDistribution文件夹是Windows操作系统中用于存储Windows Update服务下载的更新文件及其相关临时文件的目录。当用户遇到Windows Update更新失败时,通常会建议清理这个文件夹中的内容,以便于重新下载更新包,解决由于旧的或损坏的更新文件导致的更新失败问题。
2023-02-16 16:18:33
136
转载
转载文章
...").bind("click", function () { showUploadDiv(); }); function showUploadDiv() { $("uploadMsg").empty(); $.fancybox({ type:'inline', width:400, href:'uploadUserHead' }); }//fancybox弹出层 上传的处理代码 Servlet服务端处理层(commonupload实现)服务器端处理代码 上传的处理代码 $(function () { $("uploadFrom").ajaxForm({ beforeSubmit:checkImg, error:function(data,status){ alert(status+' , '+data); $("uploadMsg").html('上传文件超过1M!'); }, success:function (data,status) { try{ var msg = $.parseJSON(data); if (msg.code == 200) { //如果成功提交 javascript:$.fancybox.close(); $("uploadUserHead").hide(); var data = msg.object; $("editImg").attr("src", data.path).show(); $("preview1").attr("src", data.path).show(); $(".zoom").show(); $("width").val(data.width); $("height").val(data.height); $("oldImgPath").val(data.realPath); $("imgFileExt").val(data.fileExt); var api, jcrop_api, boundx, boundy; $('editImg').Jcrop({ onChange:updatePreview, onSelect:updatePreview, aspectRatio:1, bgOpacity:0.5, bgColor:'white', addClass:'jcrop-light' }, function () { api = this; api.setSelect([130, 65, 130 + 350, 65 + 285]); api.setOptions({ bgFade:true }); api.ui.selection.addClass('jcrop-selection'); var bounds = this.getBounds(); boundx = bounds[0]; boundy = bounds[1]; jcrop_api = this; }); function updatePreview(c) { if (parseInt(c.w) > 0) { var rx = 80 / c.w; var ry = 80 / c.h; $('preview1').css({ width:Math.round(rx boundx) + 'px', height:Math.round(ry boundy) + 'px', marginLeft:'-' + Math.round(rx c.x) + 'px', marginTop:'-' + Math.round(ry c.y) + 'px' }); } jQuery('x').val(c.x); jQuery('y').val(c.y); jQuery('x2').val(c.x2); jQuery('y2').val(c.y2); jQuery('w').val(c.w); jQuery('h').val(c.h); } } if (msg.code == 204) { $("uploadMsg").html(msg.msg); } }catch (e){ $("uploadMsg").html('上传文件超过1M!'); } } }); }); //服务器端处理代码 String tempSavePath = ConfigurationUtils.get("user.resource.dir"); //上传的图片零时保存路径 String tempShowPath = ConfigurationUtils.get("user.resource.url"); //用户保存的头像路径 if(tempSavePath.equals("/img")) { tempSavePath=sc.getRealPath("/")+tempSavePath; } Msg msg = new Msg(); msg.setCode(204); msg.setMsg("上传头像失败!"); String type = request.getParameter("type"); if (!Strings.isNullOrEmpty(type) && type.equals("first")) { request.setCharacterEncoding("utf-8"); DiskFileItemFactory factory = new DiskFileItemFactory(); ServletFileUpload servletFileUpload = new ServletFileUpload(factory); try { List items = servletFileUpload.parseRequest(request); Iterator iterator = items.iterator(); while (iterator.hasNext()) { FileItem item = (FileItem) iterator.next(); if (!item.isFormField()) { { File tempFile = new File(item.getName()); File saveTemp = new File(tempSavePath+"/tempImg/"); String getItemName=tempFile.getName(); String fileName = UUID.randomUUID()+"." +getItemName.substring(getItemName.lastIndexOf(".") + 1, getItemName.length()); File saveDir = new File(tempSavePath+"/tempImg/", fileName); //如果目录不存在,创建。 if (saveTemp.exists() == false) { if (!saveTemp.mkdir()) { // 创建失败 saveTemp.getParentFile().mkdir(); saveTemp.mkdir(); } else { } } if (saveDir.exists()) { log.info("存在同名文件···"); saveDir.delete(); } item.write(saveDir); log.info("上传头像成功!"+saveDir.getName()); msg.setCode(200); msg.setMsg("上传头像成功!"); Image image = new Image(); BufferedImage bufferedImage = null; try { bufferedImage = ImageIO.read(saveDir); } catch (IOException e) { e.printStackTrace(); } image.setHeight(bufferedImage.getHeight()); image.setWidth(bufferedImage.getWidth()); image.setPath(tempShowPath+ "/tempImg/" + fileName); log.info(image.getPath()); image.setRealPath(tempSavePath+"/tempImg/"+ fileName); image.setFileExt(fileName.substring(fileName.lastIndexOf(".") + 1, fileName.length())); msg.setObject(image); } } else { log.info("" + item.getFieldName()); } } } catch (Exception ex) { log.error("上传用户头像图片异常!"); ex.printStackTrace(); } finally { AppHelper.returnJsonAjaxForm(response, msg); } } 上传成功后,可以看到照片和照片的预览效果。看图: 上传头像之后的效果 Friday, October 05, 2012 第二步:编辑和保存头像 选中图中的区域,保存头像,就完成头像的修改。 修改之后的效果入下: 修改之后的头像(因为传了一张动态图片,得到的跟上图有些不同) 实现细节: 首先用了一个js控件:Jcrop,有兴趣的屌丝可以去搜一下,然后,利用上传之后的图片和之前的选定区域,完成了一个截图,保存为用户的头像。 连接层的js: $("saveHead").bind("click", function () { var width = $("width").val(); var height = $("height").val(); var oldImgPath = $("oldImgPath").val(); var imgFileExt = $("imgFileExt").val(); var x = $('x').val(); var y = $('y').val(); var w = $('w').val(); var h = $('h').val(); $.ajax({ url:'/imgCrop', type:'post', data:{x:x, y:y, w:w, h:h, width:width, height:height, oldImgPath:oldImgPath, fileExt:imgFileExt}, datatype:'json', success:function (msg) { if (msg.code == 200) { $("avatar").attr("src", msg.object); forword('/nav', 'index'); } else { alert(msg.msg); } } }); }); function checkImg() { //限制上传文件的大小和后缀名 var filePath = $("input[name='uploadImg']").val(); if (!filePath) { $("uploadMsg").html("请选择上传文件!").show(); return false; } else { var extStart = filePath.lastIndexOf("."); var ext = filePath.substring(extStart, filePath.length).toUpperCase(); if (ext != ".PNG" && ext != ".GIF" && ext != ".JPG") { $("uploadMsg").html("图片限于png,gif,jpg格式!").show(); return false; } } return true; } 服务器端处理代码: String savePath = ConfigurationUtils.get("user.resource.dir"); //上传的图片保存路径 String showPath = ConfigurationUtils.get("user.resource.url"); //显示图片的路径 if(savePath.equals("/img")) { savePath=sc.getRealPath("/")+savePath; } int userId = AppHelper.getUserId(request); String userName=AppHelper.getUserName(request); Msg msg = new Msg(); msg.setCode(204); msg.setMsg("剪切图片失败!"); if (userId <= 0) { msg.setMsg("请先登录"); return; } // 用户经过剪辑后的图片的大小 Integer x = (int)Float.parseFloat(request.getParameter("x")); Integer y = (int)Float.parseFloat(request.getParameter("y")); Integer w = (int)Float.parseFloat(request.getParameter("w")); Integer h = (int)Float.parseFloat(request.getParameter("h")); //获取原显示图片路径 和大小 String oldImgPath = request.getParameter("oldImgPath"); Integer width = (int)Float.parseFloat(request.getParameter("width")); Integer height = (int)Float.parseFloat(request.getParameter("height")); //图片后缀 String imgFileExt = request.getParameter("fileExt"); String foldName="/"+ DateUtils.nowDatetoStrToMonth()+"/"; String imgName = foldName + UUID.randomUUID()+userName + "." + imgFileExt; //组装图片真实名称 String createImgPath = savePath + imgName; //进行剪切图片操作 ImageCut.abscut(oldImgPath,createImgPath, xwidth/300, yheight/300, wwidth/300, hheight/300); File f = new File(createImgPath); if (f.exists()) { msg.setObject(imgName); //把显示路径保存到用户信息下面。 UserService userService = userServiceProvider.get(); int rel = userService.updateUserAvatar(userId, showPath+imgName); if (rel >= 1) { msg.setCode(200); msg.setMsg("剪切图片成功!"); log.info("剪切图片成功!"); //记录日志,更新session log(showPath+imgName,userName); UserObject userObject= userService.getUserObject(userName); request.getSession().setAttribute("userObject", userObject); if (userObject != null && Strings.isNullOrEmpty(userObject.getHeadDir())) userObject.setHeadDir("/images/geren_right_01.jpg"); } else { msg.setCode(204); msg.setMsg("剪切图片失败!"); log.info("剪切图片失败!"); } } AppHelper.returnJson(response, msg); File file=new File(oldImgPath); boolean deleteFile= file.delete(); if(deleteFile==true) { log.info("删除原来图片成功"); } / 图像切割(改) @param srcImageFile 源图像地址 @param dirImageFile 新图像地址 @param x 目标切片起点x坐标 @param y 目标切片起点y坐标 @param destWidth 目标切片宽度 @param destHeight 目标切片高度 / public static void abscut(String srcImageFile, String dirImageFile, int x, int y, int destWidth, int destHeight) { try { Image img; ImageFilter cropFilter; // 读取源图像 BufferedImage bi = ImageIO.read(new File(srcImageFile)); int srcWidth = bi.getWidth(); // 源图宽度 int srcHeight = bi.getHeight(); // 源图高度 if (srcWidth >= destWidth && srcHeight >= destHeight) { Image image = bi.getScaledInstance(srcWidth, srcHeight, Image.SCALE_DEFAULT); // 改进的想法:是否可用多线程加快切割速度 // 四个参数分别为图像起点坐标和宽高 // 即: CropImageFilter(int x,int y,int width,int height) cropFilter = new CropImageFilter(x, y, destWidth, destHeight); img = Toolkit.getDefaultToolkit().createImage(new FilteredImageSource(image.getSource(), cropFilter)); BufferedImage tag = new BufferedImage(destWidth, destHeight, BufferedImage.TYPE_INT_RGB); Graphics g = tag.getGraphics(); g.drawImage(img, 0, 0, null); // 绘制缩小后的图 g.dispose(); // 输出为文件 ImageIO.write(tag, "JPEG", new File(dirImageFile)); } } catch (Exception e) { e.printStackTrace(); } } 最后一个处理的比较好的地方就是图片的存储路径问题: 我在服务器端的nginx中做了一个图片的地址映射,把图片放到了跟程序不同的路径中,每次存储图片都是存到图片路径中,客户端拿到图片的地址确实经过nginx映射过的地址。 还有就是关于限制上传图片的大小的问题: 我在服务器端显示了资源的最大大小为1M,当上传的资源超过1M,服务器自动报错413,通过异常处理,可以在客户端得到正确的提示信息。 4,总结优点和不足。 关于修改头像,这么做下来确实达到了目的,用户可以从容的修改头像,性能也还可以。但是,上传图片的大小判断是依靠服务器端来判断的,等待的时间比较久,改进的方向是使用flash控件来限制,使用flash来上传,也不会出现弹出层,这样比较大众化,更容易为用户接受一点。我会不断改进。 本篇文章为转载内容。原文链接:https://blog.csdn.net/weixin_39849287/article/details/111489534。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-07-18 10:58:17
268
转载
转载文章
...ewId,不懂这一块技术的同学第一眼看上去肯定会一脸懵逼,下面会手把手带大家写出ButtonKnife的注解使用。使用注解可以简化代码,提高开发效率。本文简单介绍下注解的使用,并对几个 Android 开源库的注解使用原理进行简析。 1、作用 标记,用于告诉编译器一些信息 ; 编译时动态处理,如动态生成代码 ; 运行时动态处理,如得到注解信息。 2、分类 标准 Annotation, 包括 Override, Deprecated, SuppressWarnings。也都是Java自带的几个 Annotation,上面三个分别表示重写函数,不鼓励使用(有更好方式、使用有风险或已不在维护),忽略某项 Warning; 元 Annotation ,@Retention, @Target, @Inherited, @Documented。元 Annotation 是指用来定义 Annotation 的 Annotation,在后面 Annotation 自定义部分会详细介绍含义; 自定义 Annotation , 表示自己根据需要定义的 Annotation,定义时需要用到上面的元 Annotation 这里只是一种分类而已,也可以根据作用域分为源码时、编译时、运行时 Annotation。通过 @interface 定义,注解名即为自定义注解名。 一、自定义注解 例如,注解@MethodInfo: @Documented@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)@Inheritedpublic @interface MethodInfo {String author() default "annotation@gmail.com";String date();int version() default 1;} 使用到了元Annotation: @Documented 是否会保存到 Javadoc 文档中 ; @Retention 保留时间,可选值 SOURCE(源码时),CLASS(编译时),RUNTIME(运行时),默认为 CLASS,值为 SOURCE 大都为 Mark Annotation,这类 Annotation 大都用来校验,比如 Override, Deprecated, SuppressWarnings ; @Target 用来指定修饰的元素,如 CONSTRUCTOR:用于描述构造器、FIELD:用于描述域、LOCAL_VARIABLE:用于描述局部变量、METHOD:用于描述方法、PACKAGE:用于描述包、PARAMETER:用于描述参数、TYPE:用于描述类、接口(包括注解类型) 或enum声明。 @Inherited 是否可以被继承,默认为 false。 注解的参数名为注解类的方法名,且: 所有方法没有方法体,没有参数没有修饰符,实际只允许 public & abstract 修饰符,默认为 public ,不允许抛异常; 方法返回值只能是基本类型,String, Class, annotation, enumeration 或者是他们的一维数组; 若只有一个默认属性,可直接用 value() 函数。一个属性都没有表示该 Annotation 为 Mark Annotation。 public class App {@MethodInfo(author = “annotation.cn+android@gmail.com”,date = "2011/01/11",version = 2)public String getAppName() {return "appname";} } 调用自定义MethodInfo 的示例,这里注解的作用实际是给方法添加相关信息: author、date、version 。 二、实战注解Butter Knife 首先,先定义一个ViewInject注解。 public @interface ViewInject { int value() default -1;} 紧接着,为刚自定义注解添加元注解。 @Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)public @interface ViewInject {int value() default -1;} 再定义一个注解LayoutInject @Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface LayoutInject {int value() default -1;} 定义一个基础的Activity。 package cn.wsy.myretrofit.annotation;import android.os.Bundle;import android.support.v7.app.AppCompatActivity;import android.util.Log;import java.lang.reflect.Field;public class InjectActivity extends AppCompatActivity {private int mLayoutId = -1;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);displayInjectLayout();displayInjectView();}/ 解析注解view id/private void displayInjectView() {if (mLayoutId <=0){return ;}Class<?> clazz = this.getClass();Field[] fields = clazz.getDeclaredFields();//获得声明的成员变量for (Field field : fields) {//判断是否有注解try {if (field.getAnnotations() != null) {if (field.isAnnotationPresent(ViewInject.class)) {//如果属于这个注解//为这个控件设置属性field.setAccessible(true);//允许修改反射属性ViewInject inject = field.getAnnotation(ViewInject.class);field.set(this, this.findViewById(inject.value()));} }} catch (Exception e) {Log.e("wusy", "not found view id!");} }}/ 注解布局Layout id/private void displayInjectLayout() {Class<?> clazz = this.getClass();if (clazz.getAnnotations() != null){if (clazz.isAnnotationPresent(LayouyInject.class)){LayouyInject inject = clazz.getAnnotation(LayouyInject.class);mLayoutId = inject.value();setContentView(mLayoutId);} }} } 首先,这里是根据映射实现设置控件的注解,java中使用反射的机制效率性能并不高。这里只是举例子实现注解。ButterKnife官方申明不是通过反射机制,因此效率会高点。 package cn.wsy.myretrofit;import android.os.Bundle;import android.widget.TextView;import cn.wsy.myretrofit.annotation.InjectActivity;import cn.wsy.myretrofit.annotation.LayouyInject;import cn.wsy.myretrofit.annotation.ViewInject;@LayoutInject(R.layout.activity_main)public class MainActivity extends InjectActivity {@ViewInject(R.id.textview)private TextView textView;@ViewInject(R.id.textview1)private TextView textview1;@ViewInject(R.id.textview2)private TextView textview2;@ViewInject(R.id.textview3)private TextView textview3;@ViewInject(R.id.textview4)private TextView textview4;@ViewInject(R.id.textview5)private TextView textview5;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);//设置属性textView.setText("OK");textview1.setText("OK1");textview2.setText("OK2");textview3.setText("OK3");textview4.setText("OK4");textview5.setText("OK5");} } 上面直接继承InjectActivity即可,文章上面也有说过:LayouyInject为什么作用域是TYPE,首先在加载view的时候,肯定是优先加载布局啊,ButterKnife也不例外。因此选择作用域在描述类,并且存在运行时。 二、解析Annotation原理 1、运行时 Annotation 解析 (1) 运行时 Annotation 指 @Retention 为 RUNTIME 的 Annotation,可手动调用下面常用 API 解析 method.getAnnotation(AnnotationName.class);method.getAnnotations();method.isAnnotationPresent(AnnotationName.class); 其他 @Target 如 Field,Class 方法类似 。 getAnnotation(AnnotationName.class) 表示得到该 Target 某个 Annotation 的信息,一个 Target 可以被多个 Annotation 修饰; getAnnotations() 则表示得到该 Target 所有 Annotation ; isAnnotationPresent(AnnotationName.class) 表示该 Target 是否被某个 Annotation 修饰; (2) 解析示例如下: public static void main(String[] args) {try {Class cls = Class.forName("cn.trinea.java.test.annotation.App");for (Method method : cls.getMethods()) {MethodInfo methodInfo = method.getAnnotation(MethodInfo.class);if (methodInfo != null) {System.out.println("method name:" + method.getName());System.out.println("method author:" + methodInfo.author());System.out.println("method version:" + methodInfo.version());System.out.println("method date:" + methodInfo.date());} }} catch (ClassNotFoundException e) {e.printStackTrace();} } 以之前自定义的 MethodInfo 为例,利用 Target(这里是 Method)getAnnotation 函数得到 Annotation 信息,然后就可以调用 Annotation 的方法得到响应属性值 。 2、编译时 Annotation 解析 (1) 编译时 Annotation 指 @Retention 为 CLASS 的 Annotation,甴 apt(Annotation Processing Tool) 解析自动解析。 使用方法: 自定义类集成自 AbstractProcessor; 重写其中的 process 函数 这块很多同学不理解,实际是 apt(Annotation Processing Tool) 在编译时自动查找所有继承自 AbstractProcessor 的类,然后调用他们的 process 方法去处理。 (2) 假设之前自定义的 MethodInfo 的 @Retention 为 CLASS,解析示例如下: @SupportedAnnotationTypes({ "cn.trinea.java.test.annotation.MethodInfo" })public class MethodInfoProcessor extends AbstractProcessor {@Overridepublic boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {HashMap<String, String> map = new HashMap<String, String>();for (TypeElement te : annotations) {for (Element element : env.getElementsAnnotatedWith(te)) {MethodInfo methodInfo = element.getAnnotation(MethodInfo.class);map.put(element.getEnclosingElement().toString(), methodInfo.author());} }return false;} } SupportedAnnotationTypes 表示这个 Processor 要处理的 Annotation 名字。 process 函数中参数 annotations 表示待处理的 Annotations,参数 env 表示当前或是之前的运行环境 process 函数返回值表示这组 annotations 是否被这个 Processor 接受,如果接受后续子的 rocessor 不会再对这个 Annotations 进行处理 三、几个 Android 开源库 Annotation 原理简析 1、Retrofit (1) 调用 @GET("/users/{username}")User getUser(@Path("username") String username); (2) 定义 @Documented@Target(METHOD)@Retention(RUNTIME)@RestMethod("GET")public @interface GET {String value();} 从定义可看出 Retrofit 的 Get Annotation 是运行时 Annotation,并且只能用于修饰 Method (3) 原理 private void parseMethodAnnotations() {for (Annotation methodAnnotation : method.getAnnotations()) {Class<? extends Annotation> annotationType = methodAnnotation.annotationType();RestMethod methodInfo = null;for (Annotation innerAnnotation : annotationType.getAnnotations()) {if (RestMethod.class == innerAnnotation.annotationType()) {methodInfo = (RestMethod) innerAnnotation;break;} }……} } RestMethodInfo.java 的 parseMethodAnnotations 方法如上,会检查每个方法的每个 Annotation, 看是否被 RestMethod 这个 Annotation 修饰的 Annotation 修饰,这个有点绕,就是是否被 GET、DELETE、POST、PUT、HEAD、PATCH 这些 Annotation 修饰,然后得到 Annotation 信息,在对接口进行动态代理时会掉用到这些 Annotation 信息从而完成调用。 因为 Retrofit 原理设计到动态代理,这里只介绍 Annotation。 2、Butter Knife (1) 调用 @InjectView(R.id.user) EditText username; (2) 定义 @Retention(CLASS) @Target(FIELD)public @interface InjectView {int value();} 可看出 Butter Knife 的 InjectView Annotation 是编译时 Annotation,并且只能用于修饰属性 (3) 原理 @Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {Map<TypeElement, ViewInjector> targetClassMap = findAndParseTargets(env);for (Map.Entry<TypeElement, ViewInjector> entry : targetClassMap.entrySet()) {TypeElement typeElement = entry.getKey();ViewInjector viewInjector = entry.getValue();try {JavaFileObject jfo = filer.createSourceFile(viewInjector.getFqcn(), typeElement);Writer writer = jfo.openWriter();writer.write(viewInjector.brewJava());writer.flush();writer.close();} catch (IOException e) {error(typeElement, "Unable to write injector for type %s: %s", typeElement, e.getMessage());} }return true;} ButterKnifeProcessor.java 的 process 方法如上,编译时,在此方法中过滤 InjectView 这个 Annotation 到 targetClassMap 后,会根据 targetClassMap 中元素生成不同的 class 文件到最终的 APK 中,然后在运行时调用 ButterKnife.inject(x) 函数时会到之前编译时生成的类中去找。 3、ActiveAndroid (1) 调用 @Column(name = “Name") public String name; (2) 定义 @Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface Column {……} 可看出 ActiveAndroid 的 Column Annotation 是运行时 Annotation,并且只能用于修饰属性 (3) 原理 Field idField = getIdField(type);mColumnNames.put(idField, mIdName);List<Field> fields = new LinkedList<Field>(ReflectionUtils.getDeclaredColumnFields(type));Collections.reverse(fields);for (Field field : fields) {if (field.isAnnotationPresent(Column.class)) {final Column columnAnnotation = field.getAnnotation(Column.class);String columnName = columnAnnotation.name();if (TextUtils.isEmpty(columnName)) {columnName = field.getName();}mColumnNames.put(field, columnName);} } TableInfo.java 的构造函数如上,运行时,得到所有行信息并存储起来用来构件表信息。 ———————————————————————— 最后一个问题,看看这段代码最后运行结果: public class Person {private int id;private String name;public Person(int id, String name) {this.id = id;this.name = name;}public boolean equals(Person person) {return person.id == id;}public int hashCode() {return id;}public static void main(String[] args) {Set<Person> set = new HashSet<Person>();for (int i = 0; i < 10; i++) {set.add(new Person(i, "Jim"));}System.out.println(set.size());} } 答案:示例代码运行结果应该是 10 而不是 1,这个示例代码程序实际想说明的是标记型注解 Override 的作用,为 equals 方法加上 Override 注解就知道 equals 方法的重载是错误的,参数不对。 本篇文章为转载内容。原文链接:https://blog.csdn.net/csdn_aiyang/article/details/81564408。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-03-28 22:30:35
104
转载
转载文章
...容器引擎,它使用容器技术将应用程序及其依赖项打包在一起,形成可移植的、自包含的运行环境。在本文中,Docker 用于创建和管理 MySQL 数据库服务的容器实例,通过提供预配置的 MySQL 镜像,使得用户能够快速启动并配置 MySQL 服务器。 MySQL 镜像 , MySQL 镜像是 Docker 中的一个预构建软件包,其中包含了运行 MySQL 数据库服务器所需的所有文件和配置。在 Docker 环境中,通过拉取并运行特定版本(如 8.0 或 5.7)的 MySQL 镜像,可以轻松创建一个新的 MySQL 容器实例,并根据需要通过环境变量等方式进行配置。 数据卷(Data Volume) , 在 Docker 中,数据卷是一个可供多个容器之间共享和持久化存储数据的区域,即使容器停止或删除,数据也能得到保留。在文中提到,可以通过 -v 参数将主机上的目录挂载为容器内的 MySQL 数据目录(例如 /var/lib/mysql),这样 MySQL 的数据库文件就能持久存储在主机系统上,而不仅仅存在于容器内部,从而实现数据持久化。 环境变量(Environment Variables) , 环境变量是在操作系统进程中维护的一系列命名值,它们提供了影响进程行为的方法。在 Docker 和 MySQL 的结合使用中,环境变量被用来传递配置信息给 MySQL 容器,比如设置根用户的密码 (MYSQL_ROOT_PASSWORD)、创建新用户和数据库 (MYSQL_USER 和 MYSQL_DATABASE) 等。这些变量在容器启动时被读取,并用于初始化和配置 MySQL 实例。 docker-entrypoint-initdb.d 目录 , 这是在官方 MySQL Docker 镜像中的一个特殊目录,当首次启动 MySQL 容器且需要初始化新数据库实例时,Docker 会自动执行该目录下所有扩展名为 .sh、.sql 和 .sql.gz 的文件。这个机制允许用户在容器启动过程中自定义数据库初始化脚本,用以填充数据或执行其他数据库初始化任务。
2023-05-29 17:31:06
101
转载
转载文章
...'test'labelIndex = 0imageIndex = 0i = 0lbdata = open(label, 'rb').read()magic, nums = struct.unpack_from(">II", lbdata, labelIndex)labelIndex += struct.calcsize('>II')imgdata = open(image, "rb").read()magic, nums, numRows, numColumns = struct.unpack_from('>IIII', imgdata, imageIndex)imageIndex += struct.calcsize('>IIII')for i in range(nums):label = struct.unpack_from('>B', lbdata, labelIndex)[0]labelIndex += struct.calcsize('>B')im = struct.unpack_from('>784B', imgdata, imageIndex)imageIndex += struct.calcsize('>784B')im = np.array(im, dtype='uint8')img = im.reshape(28, 28)save_name = os.path.join(save, '{}_{}_{}.jpg'.format(prefix, i, label))cv2.imwrite(save_name, img)if __name__ == '__main__':需要更改的文件路径!!!!!!此处是原始数据集位置train_images = 'C:/Users/ASUS/Desktop/train-images.idx3.ubyte'train_labels = 'C:/Users/ASUS/Desktop/train-labels.idx1.ubyte'test_images ='C:/Users/ASUS/Desktop/t10k-images.idx3.ubyte'test_labels = 'C:/Users/ASUS/Desktop/t10k-labels.idx1.ubyte'此处是我们将转化后的数据集保存的位置save_train ='C:/Users/ASUS/Desktop/MNIST/train_images/'save_test ='C:/Users/ASUS/Desktop/MNIST/test_images/'if not os.path.exists(save_train):os.makedirs(save_train)if not os.path.exists(save_test):os.makedirs(save_test)trans(test_images, test_labels, save_test)trans(train_images, train_labels, save_train) 2.2 将图片按照标签分类到具体文件夹 文章参考链接:python实现根据文件名自动分类转移至不同的文件夹 注意:为了适合这个数据集和我的win11系统对代码进行了一点调整,由于数据很多如果只需要部分数据一定要将那些数据单独放在一个文件夹。 导入库import osimport shutil 当前文件夹所在的路径,使用时需要进行修改current_path = 'C:/Users/ASUS/Desktop/MNIST/test'print('当前文件夹为:' + current_path) 读取该路径下的文件filename_list = os.listdir(current_path) 建立文件夹并且进行转移 假设原图片名称 test_001_2.jpgfor filename in filename_list:name1, name2, name3 = filename.split('_') name1 = test name2 = 001 name3 = 2.jpgname4, name5 = name3.split('.') name4 = 2 name5 = jpgif name5 == 'jpg' or name5 == 'png':try:os.mkdir(current_path+'/'+name4)print('成功建立文件夹:'+name4)except:passtry:shutil.move(current_path+'/'+filename, current_path+'/'+name4[:])print(filename+'转移成功!')except Exception as e:print('文件 %s 转移失败' % filename)print('转移错误原因:' + e)print('整理完毕!') 2.3 数据存在的缺陷 数据集内的图片数量很多,由于后面介绍的云端训练的限制,只能采用部分数据(本人采用的是1000张,大家可以自行增减数目)。 数据集为国外的数据集,很多数字写的跟我们不一样。如果想要更好的适用于我们国内的场景,可以对数据集进行手动的筛选。下面是他们写的数字2: 可以看出跟我们的不一样,不过数据集中仍然存在跟常规书写的一样的,我们需要进行人为的筛选。 2.4 优化建议(核心) 分析发现,部分数字精度不高的原因主要是国外手写很随意,我们可以通过调整网络参数(如下)、人为筛选数据(如上)、增大数据集等方式进行优化。 二、模型训练 主要参考文章:通过云端自动生成openmv的神经网络模型,进行目标检测 !!!唯一不同的点是我图像参数设置的是灰度而不是上述文章的RGB。 下面是我模型训练时的参数设置(仅供参考): 通过混淆矩阵可以看出,主要的错误在于数字2、6、8。我们可以通过查看识别错误的数字来分析可能的原因。 三、项目实现 !!!我们需要先将上述步骤中导出文件中的所有内容复制粘贴带OpenMV中自带的U盘中。然后将其中的.py文件名称改为main 1. 代码实现 本人修改后的完整代码展示如下,使用的是OpenMV IDE(官网下载): 数字识别后控制直流电机转速from pyb import Pin, Timerimport sensor, image, time, os, tf, math, random, lcd, uos, gc 根据识别的数字输出不同占比的PWM波def run(number):if inverse == True:ain1.low()ain2.high()else:ain1.high()ain2.low()ch1.pulse_width_percent(abs(number10)) 具体参数调整自行搜索sensor.reset() 初始化感光元件sensor.set_pixformat(sensor.GRAYSCALE) set_pixformat : 设置像素模式(GRAYSCALSE : 灰色; RGB565 : 彩色)sensor.set_framesize(sensor.QQVGA2) set_framesize : 设置处理图像的大小sensor.set_windowing((128, 160)) set_windowing : 设置提取区域大小sensor.skip_frames(time = 2000) skip_frames :跳过2000ms再读取图像lcd.init() 初始化lcd屏幕。inverse = False True : 电机反转 False : 电机正转ain1 = Pin('P1', Pin.OUT_PP) 引脚P1作为输出ain2 = Pin('P4', Pin.OUT_PP) 引脚P4作为输出ain1.low() P1初始化低电平ain2.low() P4初始化低电平tim = Timer(2, freq = 1000) 采用定时器2,频率为1000Hzch1 = tim.channel(4, Timer.PWM, pin = Pin('P5'), pulse_width_percent = 100) 输出通道1 配置PWM模式下的定时器(高电平有效) 端口为P5 初始占空比为100%clock = time.clock() 设置一个时钟用于追踪FPS 加载模型try:net = tf.load("trained.tflite", load_to_fb=uos.stat('trained.tflite')[6] > (gc.mem_free() - (641024)))except Exception as e:print(e)raise Exception('Failed to load "trained.tflite", did you copy the .tflite and labels.txt file onto the mass-storage device? (' + str(e) + ')') 加载标签try:labels = [line.rstrip('\n') for line in open("labels.txt")]except Exception as e:raise Exception('Failed to load "labels.txt", did you copy the .tflite and labels.txt file onto the mass-storage device? (' + str(e) + ')') 不断的进行运行while(True):clock.tick() 更新时钟img = sensor.snapshot().binary([(0,64)]) 抓取一张图像以灰度图显示lcd.display(img) 拍照并显示图像for obj in net.classify(img, min_scale=1.0, scale_mul=0.8, x_overlap=0.5, y_overlap=0.5): 初始化最大值和标签max_num = -1max_index = -1print("\nPredictions at [x=%d,y=%d,w=%d,h=%d]" % obj.rect())img.draw_rectangle(obj.rect()) 预测值和标签写成一个列表predictions_list = list(zip(labels, obj.output())) 输出各个标签的预测值,找到最大值进行输出for i in range(len(predictions_list)):print('%s 的概率为: %f' % (predictions_list[i][0], predictions_list[i][1]))if predictions_list[i][1] > max_num:max_num = predictions_list[i][1]max_index = int(predictions_list[i][0])run(max_index)print('该数字预测为:%d' % max_index)print('FPS为:', clock.fps())print('PWM波占空比为: %d%%' % (max_index10)) 2. 采用器件 使用的器件为OpenMV4 H7 Plus和L298N以及常用的直流电机。关键是找到器件的引脚图,再进行简单的连线即可。 参考文章:【L298N驱动模块学习笔记】–openmv驱动 参考文章:【openmv】原理图 引脚图 2. 注意事项 上述代码中我用到了lcd屏幕,主要是为了方便离机操作。使用过程中,OpenMV的lcd初始化时会重置端口,所有我们在输出PWM波的时候一定不要发生引脚冲突。我们可以在OpenMV官网查看lcd用到的端口: 可以看到上述用到的是P0、P2、P3、P6、P7和P8。所有我们输出PWM波时要避开这些端口。下面是OpenMV的PWM资源: 总结 本人第一次自己做东西也是第一次使用python,所以代码和项目写的都很粗糙,只是简单的识别数字控制直流电机。我也是四处借鉴修改后写下的大小,这篇文章主要是为了给那些像我一样的小白们提供一点帮助,减少大家查找资料的时间。模型的缺陷以及改进方法上述中已经说明,如果我有写错或者大家有更好的方法欢迎大家告诉我,大家一起进步! 本篇文章为转载内容。原文链接:https://blog.csdn.net/weixin_57100435/article/details/130740351。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2024-01-10 08:44:41
282
转载
转载文章
...删除相应内容。 SQLite损坏修复 问题背景 目前后台服务器应该是不保存聊天记录,口袋助理iOS端的所有聊天记录都存储在一个 SQLite 数据库中,一旦这个数据库损坏,将会丢失用户的聊天记录。 解决思路 预防措施: SQLite 是一个号称每行代码都有对应测试的成熟框架,其代码问题导致的 bug 非常少见。而一般损坏原因主要有3点: 空间不足 设备断电或 AppCrash 文件 sync 失败 针对空间不足: 通过中度的使用和观察,我发现 iOS 端的空间占用是相对合理的,并没有对存储空间的明显浪费。并且 App 会在数据库写入时检查可用空间,如果不足时会抛出空间不足的提示。 针对设备断电或App崩溃: 设备断电属于不可抗力。而 App 崩溃目前我们准备上线 APM 监控平台,预期在一到两个版本的迭代中把崩溃率降低到千分之一以下的行业优秀水平。 针对文件 sync 失败: 调整 synchronous = FULL , 保证每个事务的操作都能写入文件。目前CoreData的默认配置项。 调整 fullfsync = 1 , 保证写入文件顺序和提交顺序一致,拒绝设备重排顺序以优化性能。此项会降低性能。对比得出写入性能大概降低至默认值的25%左右。 优化效果: 根据微信的实践,调整配置项后,损坏率可以降低一半,但并不能完全避免损坏,所以我们还是需要补救措施。 补救措施: 通过查阅 SQLite 的相关资料,发现修复损坏数据库的两种思路和四种方案。 思路一:数据导出 .dump修复 从 master 表中读出一个个表的信息,根据根节点地址和创表语句来 select 出表里的数据,能 select 多少是多少,然后插入到一个新 DB 中。 每个SQLite DB都有一个sqlite_master表,里面保存着全部table和index的信息(table本身的信息,不包括里面的数据哦),遍历它就可以得到所有表的名称和 CREATE TABLE ...的SQL语句,输出CREATE TABLE语句,接着使用SELECT FROM ... 通过表名遍历整个表,每读出一行就输出一个INSERT语句,遍历完后就把整个DB dump出来了。 这样的操作,和普通查表是一样的,遇到损坏一样会返回SQLITE_CORRUPT,我们忽略掉损坏错误, 继续遍历下个表,最终可以把所有没损坏的表以及损坏了的表的前半部分读取出来。将 dump 出来的SQL语句逐行执行,最终可以得到一个等效的新DB。 思路二:数据备份 拷贝: 不能再直白的方式。由于SQLite DB本身是文件(主DB + journal 或 WAL), 直接把文件复制就能达到备份的目的。 .dump备份: 上一个恢复方案用到的命令的本来目的。在DB完好的时候执行.dump, 把 DB所有内容输出为 SQL语句,达到备份目的,恢复的时候执行SQL即可。 Backup API: SQLite自身提供的一套备份机制,按 Page 为单位复制到新 DB, 支持热备份。 综合思路:备份master表+数据导出 WCDB框架: 数据库完整时备份master表,数据库损坏时通过使用已备份的master表读取损坏数据库来恢复数据。成功率大概是70%。缺点在于我们目前项目使用的是CoreData框架,迁移成本非常的高。没有办法使用。 补救措施选型原则: 这么多的方案孰优孰劣?作为一个移动APP,我们追求的就是用户体验,根据资料推断只有万分之一不到的用户会发生DB损坏,不能为了极个别牺牲全体用户的体验。不影响用户体验的方法就是好方案。主要考量指标如下: 一:恢复成功率 由于牵涉到用户核心数据,“姑且一试”的方案是不够的,虽说 100% 成功率不太现实,但 90% 甚至 99% 以上的成功率才是我们想要的。 二:备份大小: 原本用户就可能有2GB 大的 DB,如果备份数据本身也有2GB 大小,用户想必不会接受。 三:备份性能: 性能则主要影响体验和备份成功率,作为用户不感知的功能,占用太多系统资源造成卡顿 是不行的,备份耗时越久,被系统杀死等意外事件发生的概率也越高。 数据导出方案考量: 恢复成功率大概是30%。不需要事先备份,故备份大小和备份性能都是最优的。 备份方案考量: 备份方案的理论恢复成功率都为100%,需要考量的即为备份大小和性能。 拷贝:备份大小等于原文件大小。备份性能最好,直接拷贝文件,不需要运算。 Backup API: 备份大小等于原文件大小。备份性能最差,原因是热备份,需要用到锁机制。 .dump:因为重新进行了排序,备份大小小于原文件。备份性能居中,需要遍历数据库生成语句。 可以看出,比较折中的选择是 Dump ,备份大小具有明显优势,备份性能尚可,恢复性能较差但由于需要恢复的场景较少,算是可以接受的短板。 深入钻研 即使优化后的方案,对于大DB备份也是耗时耗电,对于移动APP来说,可能未必有这样的机会做这样重度的操作,或者频繁备份会导致卡顿和浪费使用空间。 备份思路的高成本迫使我们从另外的方案考虑,于是我们再次把注意力放在之前的Dump方案。 Dump 方案本质上是尝试从坏DB里读出信息,这个尝试一般来说会出现两种结果: DB的基本格式仍然健在,但个别数据损坏,读到损坏的地方SQLite返回SQLITE_CORRUPT错误, 但已读到的数据得以恢复。 基本格式丢失(文件头或sqlite_master损坏),获取有哪些表的时候就返回SQLITE_CORRUPT, 根本没法恢复。 第一种可以算是预期行为,毕竟没有损坏的数据能部分恢复。从成功率来看,不少用户遇到的是第二种情况,这种有没挽救的余地呢? 要回答这个问题,先得搞清楚sqlite_master是什么。它是一个每个SQLite DB都有的特殊的表, 无论是查看官方文档Database File Format,还是执行SQL语句 SELECT FROM sqlite_master;,都可得知这个系统表保存以下信息: 表名、类型(table/index)、 创建此表/索引的SQL语句,以及表的RootPage。sqlite_master的表名、表结构都是固定的, 由文件格式定义,RootPage 固定为 page 1。 正常情况下,SQLite 引擎打开DB后首次使用,需要先遍历sqlite_master,并将里面保存的SQL语句再解析一遍, 保存在内存中供后续编译SQL语句时使用。假如sqlite_master损坏了无法解析,“Dump恢复”这种走正常SQLite 流程的方法,自然会卡在第一步了。为了让sqlite_master受损的DB也能打开,需要想办法绕过SQLite引擎的逻辑。 由于SQLite引擎初始化逻辑比较复杂,为了避免副作用,没有采用hack的方式复用其逻辑,而是决定仿造一个只可以 读取数据的最小化系统。 虽然仿造最小化系统可以跳过很多正确性校验,但sqlite_master里保存的信息对恢复来说也是十分重要的, 特别是RootPage,因为它是表对应的B-tree结构的根节点所在地,没有了它我们甚至不知道从哪里开始解析对应的表。 sqlite_master信息量比较小,而且只有改变了表结构的时候(例如执行了CREATE TABLE、ALTER TABLE 等语句)才会改变,因此对它进行备份成本是非常低的,一般手机典型只需要几毫秒到数十毫秒即可完成,一致性也容易保证, 只需要执行了上述语句的时候重新备份一次即可。有了备份,我们的逻辑可以在读取DB自带的sqlite_master失败的时候 使用备份的信息来代替。 到此,初始化必须的数据就保证了,可以仿造读取逻辑了。我们常规使用的读取DB的方法(包括dump方式恢复), 都是通过执行SQL语句实现的,这牵涉到SQLite系统最复杂的子系统——SQL执行引擎。我们的恢复任务只需要遍历B-tree所有节点, 读出数据即可完成,不需要复杂的查询逻辑,因此最复杂的SQL引擎可以省略。同时,因为我们的系统是只读的, 写入恢复数据到新 DB 只要直接调用 SQLite 接口即可,因而可以省略同样比较复杂的B-tree平衡、Journal和同步等逻辑。 最后恢复用的最小系统只需要: VFS读取部分的接口(Open/Read/Close),或者直接用stdio的fopen/fread、Posix的open/read也可以 B-tree解析逻辑 Database File Format 详细描述了SQLite文件格式, 参照之实现B-tree解析可读取 SQLite DB。 实现了上面的逻辑,就能读出DB的数据进行恢复了,但还有一个小插曲。我们知道,使用SQLite查询一个表, 每一行的列数都是一致的,这是Schema层面保证的。但是在Schema的下面一层——B-tree层,没有这个保证。 B-tree的每一行(或者说每个entry、每个record)可以有不同的列数,一般来说,SQLite插入一行时, B-tree里面的列数和实际表的列数是一致的。但是当对一个表进行了ALTER TABLE ADD COLUMN操作, 整个表都增加了一列,但已经存在的B-tree行实际上没有做改动,还是维持原来的列数。 当SQLite查询到ALTER TABLE前的行,缺少的列会自动用默认值补全。恢复的时候,也需要做同样的判断和支持, 否则会出现缺列而无法插入到新的DB。 解析B-tree方案上线后,成功率约为78%。这个成功率计算方法为恢复成功的 Page 数除以总 Page 数。 由于是我们自己的系统,可以得知总 Page 数,使用恢复 Page 数比例的计算方法比人数更能反映真实情况。 B-tree解析好处是准备成本较低,不需要经常更新备份,对大部分表比较少的应用备份开销也小到几乎可以忽略, 成功恢复后能还原损坏时最新的数据,不受备份时限影响。 坏处是,和Dump一样,如果损坏到表的中间部分,比如非叶子节点,将导致后续数据无法读出。 落地实践: 剥离封装RepairKit: 从WCDB框架中,剥离修复组件,并且封装其C++的原始API为OC管理类。 备份 master 表的时机: 我们发现 SQLite 里面 B+树 算法的实现是 向下分裂 的,也就是说当一个叶子页满了需要分裂时,原来的叶子页会成为内部节点,然后新申请两个页作为他的叶子页。这就保证了根节点一旦下来,是再也不会变动的。master 表只会在新创建表或者删除一个表时才会发生变化,而CoreData的机制表明每一次数据库的变动都要改动版本标识,那么我通过缓存和查询版本标识的变动来确定何时进行备份,避免频繁备份。 备份文件有效性: 既然 DB 可以损坏,那么这个备份文件也会损坏,怎么办呢?我用了双备份,每一个版本备份两个文件,如果一个备份恢复失败,就会启动另一个备份文件恢复。 介入恢复时机: 当CoreData初始化SQLite前,校验SQLite的Head完整性,如果不完整,进行介入修复。 经过我深入研究证明了这已经是最佳做法。 本篇文章为转载内容。原文链接:https://blog.csdn.net/a66666225/article/details/81637368。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-11-23 18:22:40
127
转载
Golang
...r := http.ListenAndServe(":8080", nil) if err != nil { panic(err) } } 这段代码虽然简单,但它背后却隐藏着Go的魔力。嘿,你有没有试过访问这个地址:http://localhost:8080/username?当你这么做的时候,Go 这家伙就会偷偷摸摸地给你派来一个小帮手——一个协程,专门负责处理你的请求。而且更贴心的是,它完全不用你去管什么线程池那些听起来就头大的复杂玩意儿,简直是太省心了吧! 当然了,光靠协程还不够。为了确保程序的健壮性,我们需要合理地利用通道(channel)来进行通信。比如下面这个简单的生产者-消费者模型: go package main import ( "fmt" "time" ) func producer(ch chan<- int) { for i := 0; i < 5; i++ { ch <- i fmt.Println("Produced:", i) time.Sleep(500 time.Millisecond) } close(ch) } func consumer(ch <-chan int) { for num := range ch { fmt.Println("Consumed:", num) } } func main() { ch := make(chan int) go producer(ch) consumer(ch) } 在这个例子中,producer函数向通道发送数据,而consumer函数从通道接收数据。用这种方法,咱们就能又优雅又稳妥地搞定多线程里的同步难题,还不用担心被死锁给缠上。 --- 3. 内存管理 GC的奥秘 接下来谈谈内存管理。Go的垃圾回收器(GC)是它的一大亮点。就像用老式工具编程一样,C/C++这种传统语言就得让程序员自己动手去清理内存,稍不留神,就可能搞出内存泄漏,或者戳到那些讨厌的野指针,简直让人头大!而Go则完全解放了我们的双手,它会自动帮你清理不再使用的内存。 不过,GC也不是万能的。有时候,如果你对性能要求特别高,可能会遇到GC停顿的问题。为了解决这个问题,Go团队一直在优化GC算法。最新版本中引入了分代GC(Generational GC),大幅降低了停顿时间。 那么,我们在实际开发中应该如何减少GC的压力呢?最直接的方法就是尽量避免频繁的小对象分配。比如,我们可以复用一些常见的结构体,而不是每次都新建它们: go type Buffer struct { data []byte } func NewBuffer(size int) Buffer { return &Buffer{data: make([]byte, size)} } func (b Buffer) Reset() { b.data = b.data[:0] } func main() { buf := NewBuffer(1024) for i := 0; i < 100; i++ { buf.Reset() // 使用buf... } } 在这个例子中,我们通过Reset()方法复用了同一个Buffer实例,而不是每次都调用make([]byte, size)重新创建一个新的切片。这样可以显著降低GC的压力。 --- 4. 网络优化 TCP/IP的实战 再来说说网络优化。Go的net包提供了强大的网络编程支持,无论是HTTP、WebSocket还是普通的TCP/UDP,都能轻松搞定。特别是对那些高性能服务器而言,怎么才能又快又稳地搞定海量连接,这简直就是一个绕不开的大难题啊! 举个例子,假设我们要实现一个简单的HTTP长连接服务器。传统的做法可能是监听端口,然后逐个处理请求。但这种方式效率不高,特别是在高并发场景下。Go提供了一个更好的解决方案——使用net/http包的Serve方法: go package main import ( "log" "net/http" ) func handler(w http.ResponseWriter, r http.Request) { w.Write([]byte("Hello, World!")) } func main() { http.HandleFunc("/", handler) log.Fatal(http.ListenAndServe(":8080", nil)) } 这段代码看起来很简单,但它实际上已经具备了处理大量并发连接的能力。为啥呢?就是因为Go语言里的http.Server自带了一个超级能打的“工具箱”,里面有个高效的连接池和请求队列,遇到高并发的情况时,它就能像一个经验丰富的老司机一样,把各种请求安排得明明白白,妥妥地hold住场面! 当然,如果你想要更底层的控制,也可以直接使用net包来编写TCP服务器。比如下面这个简单的TCP回显服务器: go package main import ( "bufio" "fmt" "net" ) func handleConnection(conn net.Conn) { defer conn.Close() reader := bufio.NewReader(conn) for { message, err := reader.ReadString('\n') if err != nil { fmt.Println("Error reading:", err) break } fmt.Print("Received:", message) conn.Write([]byte(message)) } } func main() { listener, err := net.Listen("tcp", ":8080") if err != nil { fmt.Println("Error listening:", err) return } defer listener.Close() fmt.Println("Listening on :8080...") for { conn, err := listener.Accept() if err != nil { fmt.Println("Error accepting:", err) continue } go handleConnection(conn) } } 在这个例子中,我们通过listener.Accept()不断接受客户端连接,并为每个连接启动一个协程来处理请求。这种模式非常适合处理大量短连接的场景。 --- 5. 代码结构 模块化与可扩展性 最后,我们来聊聊代码结构。一个高性能的服务器不仅仅依赖于语言特性,还需要良好的设计思路。Go语言特别推崇把程序分成小块儿来写,就像搭积木一样,每个功能都封装成独立的小模块或包。这样不仅修 bug 的时候方便找问题,写代码的时候也更容易看懂,以后想加新功能啥的也简单多了。 比如,假设我们要开发一个分布式任务调度系统,可以按照以下方式组织代码: go // tasks.go package task type Task struct { ID string Name string Param interface{} } func NewTask(id, name string, param interface{}) Task { return &Task{ ID: id, Name: name, Param: param, } } // scheduler.go package scheduler import "task" type Scheduler struct { tasks []task.Task } func NewScheduler() Scheduler { return &Scheduler{ tasks: make([]task.Task, 0), } } func (s Scheduler) AddTask(t task.Task) { s.tasks = append(s.tasks, t) } func (s Scheduler) Run() { for _, t := range s.tasks { fmt.Printf("Executing task %s\n", t.Name) // 执行任务逻辑... } } 通过这种方式,我们将任务管理和调度逻辑分离出来,使得代码更加清晰易懂。同时,这样的设计也方便未来扩展新的功能,比如添加日志记录、监控指标等功能。 --- 6. 总结与展望 好了,到这里咱们就差不多聊完了如何用Go语言进行高性能服务器开发。说实话,写着这篇文章的时候,我脑海里突然蹦出大学时那股子钻研劲儿,感觉就像重新回到那些熬夜敲代码的日子了,整个人都热血上头!Go这门语言真的太带感了,简单到没话说,效率还超高,稳定性又好得没话说,简直就是程序员的救星啊! 不过,我也想提醒大家一句:技术再好,最终还是要服务于业务需求。不管你用啥法子、说啥话,老老实实问问自己:“这招到底管不管用?是不是真的解决问题了?”这才是真本事! 希望这篇文章对你有所帮助,如果你有任何疑问或者想法,欢迎随时留言讨论!让我们一起继续探索Go的无限可能吧!
2025-04-23 15:46:59
39
桃李春风一杯酒
转载文章
...KVM是内核级虚拟化技术 KVM全称Kernel-based Virtual Machine 最上面是我们的PC的形式; 在实际的服务器上一个物理机会有多个虚拟操作系统公用这些物理资源; 然后组合成群后,就是最下面的形式; 五、虚拟化分类 1.硬件虚拟化 硬件虚拟化代表:KVM 2.软件虚拟化 软件虚拟化代表:Qemu 硬件虚拟化是需要CPU支持,如果CPU不支持将无法创建KVM虚拟机 六、虚拟化技术 全虚拟化:全虚拟化代表有:KVM 半虚拟化:半虚拟化代表有Hypervisor 针对IO层面半虚拟化要比全虚拟化要好,因为磁盘IO多一层必定会慢。一般说IO就是网络IO和磁盘IO 因为这两个相对而言是比较慢的 ; 提示: Qemu和KVM的最大区别就是,如果一台物理机内存直接4G,创建一个vm虚拟机分配内存分4G,在创建一个还可以分4G。支持超配,但是Qemu不支持; 七、虚拟化使用场景分类 服务器虚拟化:解决资源利用率低的问题 桌面虚拟化:有一些弊端,图形显示层面会有问题 应用虚拟化:没接触过,公司比较穷买不起,基本上只有银行等国企才会用Xenapp ICA 八、虚拟化工具KVM介绍 KVM 全称:Kernel-based Virtual Machine(内核级虚拟化机器) 原本由以色列人创建,现在被红帽收购 ESXI 虚拟套件,现在是免费使用 VMware vSphere Hypervisor – 安装和配置 提示:一台服务器首选ESXI 九、KVM安装 调整虚拟机 虚拟化Intel使用的是Intel VT-X ; 虚拟化AMD使用的是AMD-V 创建虚拟机步骤 1.准备虚拟机硬盘 2.需要系统iso镜像3.需要安装一个vnc的客户端来连接 查看系统环境 [root@linux-node1 ~] cat /etc/redhat-release CentOS Linux release 7.2.1511 (Core) [root@linux-node1 ~] uname -r 3.10.0-327.36.2.el7.x86_64 检查是否有vmx或者svm [root@linux-node1 ~] grep -E '(vmx|svm)' /proc/cpuinfo 安装kvm用户态模块 [root@linux-node1 ~] yum list|grep kvm libvirt-daemon-kvm.x86_64 1.2.17-13.el7_2.5 updates pcp-pmda-kvm.x86_64 3.10.6-2.el7 base qemu-kvm.x86_64 10:1.5.3-105.el7_2.7 updates qemu-kvm-common.x86_64 10:1.5.3-105.el7_2.7 updates qemu-kvm-tools.x86_64 10:1.5.3-105.el7_2.7 updates [root@linux-node1 ~] yum install qemu-kvm qemu-kvm-tools libvirt -y libvirt 用来管理kvm kvm属于内核态,不需要安装。但是需要一些类似于依赖的 kvm属于内核态,不需要安装。但是需要安装一些类似于依赖的东西 启动 [root@linux-node1 ~] systemctl start libvirtd.service [root@linux-node1 ~] systemctl enable libvirtd.service 启动之后我们可以使用ifconfig进行查看,libvirtd已经为我们安装了一个桥接网卡 libvirtd为我们启动了一个dnsmasqp,这个主要是用来dhcp连接的,这个工具会给我们的虚拟机分配IP地址 [root@linux-node1 ~] ps -ef|grep dns nobody 5233 1 0 14:27 ? 00:00:00 /sbin/dnsmasq --conf-file=/var/lib/libvirt/dnsmasq/default.conf --leasefile-ro --dhcp-script=/usr/libexec/libvirt_leaseshelper root 5234 5233 0 14:27 ? 00:00:00 /sbin/dnsmasq --conf-file=/var/lib/libvirt/dnsmasq/default.conf --leasefile-ro --dhcp-script=/usr/libexec/libvirt_leaseshelperoot 5310 2783 0 14:31 pts/0 00:00:00 grep --color=auto dns 查看磁盘空间大小 最好是20G以上 [root@linux-node1 tmp] df -h 上传镜像 提示:如果使用rz上传镜像可能会出现错误,所以我们使用dd命令,复制系统的镜像。只需要挂载上光盘即可 [root@linux-node1 opt] dd if=/dev/cdrom of=/opt/CentOS-7.2.iso [root@linux-node1 opt] ll total 33792 -rw-r--r-- 1 root root 34603008 Jun 12 18:18 CentOS-7.2-x86_64-DVD-1511.iso 下载VNC 下载地址:http://www.tightvnc.com/download/2.8.5/tightvnc-2.8.5-gpl-setup-64bit.msi 安装完VNC如下图 创建磁盘 提示: qemu-img软件包是我们安装qemu-kvm-tools 依赖给安装上的 [root@linux-node1 opt] qemu-img create -f raw /opt/CentOS-7.2-x86_64.raw 10GFormatting '/opt/Centos-7-x86_64.raw', fmt=raw size=10737418240 [root@linux-node1 opt] [root@linux-node1 opt] ll /opt/Centos-7-x86_64.raw -rw-r--r-- 1 root root 10737418240 Oct 26 14:53 /opt/Centos-7-x86_64.raw-f 制定虚拟机格式,raw是裸磁盘/opt/Centos 存放路径 10G 代表镜像大小 安装启动虚拟机的包 [root@linux-node1 tmp] yum install -y virt-install 安装虚拟机 [root@linux-node1 tmp] virt-install --help 我们可以指定虚拟机的CPU、磁盘、内存等 [root@linux-node1 opt] virt-install --name CentOS-7.2-x86_64 --virt-type kvm --ram 1024 --cdrom=/opt/CentOS-7.2.iso --disk path=/opt/CentOS-7.2-x86_64.raw --network network=default --graphics vnc,listen=0.0.0.0 --noautoconsole --name = 给虚拟机起个名字 --ram = 内存大小 --cdrom = 镜像位置,就是我们上传iso镜像的位置,我放在/tmp下了 --disk path = 指定磁盘--network network= 网络配置 default 就会用我们刚刚ifconfig里面桥接的网卡--graphics vnc,listen= 监听vnc, 分区说明 提示:我们不分交换分区,因为公有云上的云主机都是没有交换分区的 十、Libvirt介绍 libvirt是一个开源免费管理工具,可以管理KVM、VMware等 他需要起一个后台的进程,它提供了API。像openstack就是通过libvirt API来管理虚拟机 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vcp4lgAZ-1596980494935)(libvirt.jpg)] 二、KVM虚拟机和VMware区别 虚拟机监控程序(KVM)是虚拟化平台的根基。从传统供应商到各种开源替代品,可供选择的虚拟机监控程序有很多。 VMware 是一款实现虚拟化的热门产品,可以提供 ESXi 虚拟机监控程序和 vSphere 虚拟化平台。 基于内核的虚拟机(KVM)则是 Linux® 系统上的一种开源解决方案。 VMware vSphere 与 VMware ESXi VMware 可以提供 ESXi 虚拟机监控程序和 vSphere 虚拟化平台。VMware ESXi 是一个能够直接安装到物理服务器上的裸机虚拟机监控程序,可以帮你整合硬件。你可以用 VMware 的虚拟化技术来创建和部署虚拟机(VM),从而现代化改造自己的基础架构,来交付和管理各种新旧应用。 选用 VMware vSphere 后,你需要使用 VMware 的控制堆栈来管理虚拟机,而且有多个许可证授权级别可供使用。 KVM 开源虚拟化技术 KVM 是一种开源虚拟化技术,能将 Linux 内核转变成可以实现虚拟化的虚拟机监控程序,而且可以替代专有的虚拟化技术(比如 VMware 提供的专有虚拟化技术)。 迁移到基于 KVM 的虚拟化平台,你就可以检查、修改和完善虚拟机监控程序背后的源代码。能够访问源代码,就如同掌握了开启无限可能的钥匙,能够让你虚拟化传统工作负载和应用,并为云原生和基于容器的工作负载奠定基础。由于 KVM 内置于 Linux 内核中,所以使用和部署起来非常方便。 KVM 虚拟机和 VMware vSphere 的主要区别 VMware 可以提供一个完善稳定的虚拟机监控程序,以及出色的性能和多样化的功能。但是,专有虚拟化会阻碍你获得开展云、容器和自动化投资所需的资源。解除供应商锁定,你就可以任享自由、灵活与丰富的资源,从而为未来的云原生和容器化环境打下基础。 生产就绪型的 KVM 具有支持物理和虚拟基础架构的功能,可以让你以更低的运营成本为企业工作负载提供支持。相比使用 VMware vSphere 等其他解决方案,选用基于 KVM 的虚拟化选项能够带来很多优势。 开源Linux KVM的优势: 更低的总拥有成本,从而省下运营预算,用来探索现代化创新技术。 不再受供应商捆绑。无需为不用的产品付费,也不会受到软件选择限制。 跨平台互操作性:KVM 可以在 Linux 和 Windows 平台上运行,所以你可以充分利用现有的基础架构投资。 出色简便性:可以通过单个虚拟化平台,在数百个其他硬件或软件上创建、启动、停止、暂停、迁移和模板化数百个虚拟机。 卓越性能:应用在 KVM 上的运行速度比其他虚拟机监控程序都快。 开源优势:不但能访问源代码,还能灵活地与各种产品集成。 享受 Linux 操作系统的现有功能: 安全防护功能 内存管理 进程调度器 设备驱动程序 网络堆栈 红帽 KVM 企业级虚拟化的优势 选择红帽® 虚拟化,就等于选择了 KVM。红帽虚拟化是一款适用于虚拟化服务器和技术工作站的完整基础架构解决方案。红帽虚拟化基于强大的红帽企业 Linux® 平台和 KVM 构建而成,能让你轻松、敏捷、安全地使用资源密集型虚拟化工作负载。红帽虚拟化可凭借更加优越的性能、具有竞争力的价格和值得信赖的红帽环境,帮助企业优化 IT 基础架构。 红帽的虚拟化产品快速、经济、高效,能够帮助你从容应对当前的挑战,并为未来的技术发展奠定基础。VMware 等供应商提供的纵向扩展虚拟化解决方案不但成本高昂,而且无法帮助企业完成所需的转型,因而难以支持在混合云中运行云原生应用。要转而部署混合云环境,第一步要做的就是摆脱专有虚拟化。 红帽虚拟化包含 sVirt 和安全增强型 Linux(SELinux),是红帽企业 Linux 专为检测和预防当前 IT 环境中的复杂安全隐患而开发的技术。 业完成所需的转型,因而难以支持在混合云中运行云原生应用。要转而部署混合云环境,第一步要做的就是摆脱专有虚拟化。 红帽虚拟化包含 sVirt 和安全增强型 Linux(SELinux),是红帽企业 Linux 专为检测和预防当前 IT 环境中的复杂安全隐患而开发的技术。 借助红帽虚拟化,你可以尽享开源虚拟机监控程序的所有优势,还能获得企业级技术支持、更新和补丁,使你的环境保持最新状态,持续安心运行。开源和 RESTful API,以及 Microsoft Windows 的认证,可帮你实现跨平台的互操作性。提供的 API 和软件开发工具包(SDK)则有助于将我们的解决方案扩展至你现有和首选管理工具,并提供相关支持。 本篇文章为转载内容。原文链接:https://blog.csdn.net/qq_34799070/article/details/107900861。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-04-06 08:58:59
121
转载
转载文章
...nt(l[1].split(".").join(""),10)>=80,p=navigator.appVersion.match(/(iphone|ipad|ipod)/gi),s=i.devicePixelRatio||1;p||d&&d[1]>534||c||(s=1);var u=normal?1:1/s,m=r.querySelector('meta[name="viewport"]');m||(m=r.createElement("meta"),m.setAttribute("name","viewport"),r.head.appendChild(m)),m.setAttribute("content","width=device-width,user-scalable=no,initial-scale="+u+",maximum-scale="+u+",minimum-scale="+u),r.documentElement.style.fontSize=normal?"50px": a/2sn+"px"},e.exports=t["default"]}]); flex(false,100, 1);</script> 代码原理 这是阿里团队的高清方案布局代码,所谓高清方案就是利用rem的特性(我们知道默认情况下html的1rem = 16px),根据设备屏幕的DPR(设备像素比,又称DPPX,比如dpr=2时,表示1个CSS像素由4个物理像素点组成)根据设备DPR动态设置 html 的font-size为(50 dpr),同时调整页面的压缩比率(即:1/dpr),进而达到高清效果。 有何优势 引用简单,布局简便 根据设备屏幕的DPR,自动设置最合适的高清缩放。 保证了不同设备下视觉体验的一致性。(老方案是,屏幕越大元素越大;此方案是,屏幕越大,看的越多) 有效解决移动端真实1px问题(这里的1px 是设备屏幕上的物理像素) 如何使用 重要的事情说三遍! 绝不是每个地方都要用rem,rem只适用于固定尺寸! 绝不是每个地方都要用rem,rem只适用于固定尺寸! 绝不是每个地方都要用rem,rem只适用于固定尺寸! 在相当数量的布局情境中(比如底部导航元素平分屏幕宽,大尺寸元素),你必须使用百分比或者flex才能完美布局! 看过 《手机端页面自适应解决方案—rem布局》的朋友,应该对rem有所了解,这里不再赘述, 此方案也是默认 1rem = 100px,所以你布局的时候,完全可以按照设计师给你的效果图写各种尺寸啦。 比如你在效果图上量取的某个按钮元素长 55px, 宽37px ,那你直接可以这样写样式: .myBtn {width: 0.55rem;height: 0.37rem;} rem布局(进阶版)实践应用 iPhone5 下页面效果.png iPhone 6 Plus 下页面效果.png 为了让朋友们更清晰感受此方案的巨大优势,下面是源码和Demo 实践应用1(请在手机端或者手机模式下浏览效果更佳!) 实践应用2(请在手机端或者手机模式下浏览效果更佳!) 线上项目(请在手机端或者手机模式下浏览效果更佳!) 示例源码 在线Demo 常见问题说明,新手很有必要看一下(2017/1/19) 许多同学对该方案存在不少误解导致使用出现各种问题,这里统一回复下。 1.问:为啥手机网页效果图宽度是要640或者750的,我非得弄个666的不行咩? 答:老实说当然可以,不过为了规范,640或者750是相对合适的。 拿Iphone 5s 举例,它的css像素宽度是320px,由于它的dpr=2,所以它的物理像素宽度为320 × 2 = 640px,这也就是为什么,你在5s上截了一张图,在电脑上打开,它的原始宽度是640px的原因。 那 iphone 6 的截图宽度呢? 375 × 2 = 750 那 iphone 6 sp 的截图宽度呢? 414 × 3 = 1242 以此类推,你现在能明白效果图为什么一般是 640 ,750 甚至是 1242 的原因了么?(真没有歧视安卓机的意思。。。) 2.问:宽度用rem写的情况下, 在 iphone6 上没问题, 在 iphone5上会有横向滚动条,何解? 答:假设你的效果图宽度是750,在这个效果图上可能有一个宽度为7rem(高清方案默认 1rem = 100px)的元素。我们知道,高清方案的特点就是几乎完美还原效果图,也就是说,你写了一个宽度为 7rem 的元素,那么在目前主流移动设备上都是7rem。然而,iphone 5 的宽度为640,也就是6.4rem。于是横向滚动条不可避免的出现了。 怎么办呢? 这是我目前推荐的比较安全的方式:如果元素的宽度超过效果图宽度的一半(效果图宽为640或750),果断使用百分比宽度,或者flex布局。就像把等屏宽的图片宽度设为100%一样。 3.问:不是 1rem = 100px吗,为什么我的代码写了一个宽度为3rem的元素,在电脑端的谷歌浏览器上宽度只有150px? 答:先说高清方案代码,再次强调咱们的高清方案代码是根据设备的dpr动态设置html 的 font-size, 如果dpr=1(如电脑端),则html的font-size为50px,此时 1rem = 50px 如果dpr=2(如iphone 5 和 6),则html的font-size为100px,此时 1rem = 100px 如果dpr=3(如iphone 6 sp),则html的font-size为150px,此时 1rem = 150px 如果dpr为其他值,即便不是整数,如3.4 , 也是一样直接将dpr 乘以 50 。 再来说说效果图,一般来讲,我们的效果图宽度要么是640,要么是750,无论哪一个,它们对应设备的dpr=2,此时,1 rem = 50 × 2 = 100px。这也就是为什么高清方案默认1rem = 100px。而将1rem默认100px也是好处多多,可以帮你快速换算单位,比如在750宽度下的效果图,某元素宽度为53px,那么css宽度直接设为53/100=0.53rem了。 然而极少情况下,有设计师将效果图宽定为1242px,因为他手里只有一个iphone 6 sp (dpr = 3),设计完效果图刚好可以在他的iphone 6 sp里查看调整。一切完毕之后,他将这个效果图交给你来切图。由于这个效果图对应设备的dpr=3,也就是1rem = 50 × 3 = 150px。所以如果你量取了一个宽度为90px的元素,它的css宽度应该为 90/150=0.6rem。由于咱们的高清方案默认1rem=100px,为了还原效果图,你需要这样换算。当然,一个技巧就是你可以直接修改咱们的高清方案的默认设置。在代码的最后 你会看到 flex(false, 100, 1) ,将其修改成flex(false, 66.66667, 1)(感谢简友:V旅行指出此处错误! 2017/3/24)就不用那么麻烦的换算了,此时那个90px的直接写成0.9rem就可以了。 4.问:在此方案下,我如果引用了别的UI库,那些UI库的元素会显得特别小,如何解决? 答:可以这样去理解问题的原因,如果不用高清方案,别的UI库的元素在移动设备上(假设这个设备是iphone 5好了)显示是正常的,这没有问题,然后我们在这个设备上将该页面截图放到电脑上看,发现宽度是640(问答1解释过了),根据你的像素眼大致测量,你发现这个设备上的某个字体大小应该是12px,而你在电脑上测量应该是24px。 现在我们使用高清方案去还原这个页面,那么字体大小应该写为 0.24rem 才对! 所以,如果你引用了其他的UI库,为了兼容高清方案,你需要对该UI库里凡是应用px的地方做相应处理,即: a px => a0.02 rem (具体处理方式因人而异,有模块化开发经验的同学可使用类似的 px2rem 的插件去转化,也可以完全手动处理) (2017/9/9更新)然而真实情况往往更为复杂,比如,你引入了百度地图(N个样式需要处理转换);或者你引入了一个 framework;又或者你使用了 video 标签,上面默认的尺寸样式很难处理。等等这些棘手问题 面对这些情况,此时我们的高清方案如果不再压缩页面,那么以上问题将迎刃而解。 基于这样的思路,笔者对高清方案的源码做了如下修改,即添加一个叫做 normal 的参数,由它来控制页面是否压缩。 在文章顶部代码的最后,你会看到 flex(false, 100, 1),默认情况下页面是开启压缩的。 如果你需要禁止压缩,由于我们的源码执行后,直接将flex函数挂载到全局变量window上了,此时你直接在需要禁止压缩的页面执行 window.flex(true) 就可以了,而rem的用法保持不变。 有一点美中不足的是,如果禁止了页面压缩,高清屏的1像素就不能实现了,如果你必须要实现1像素,那么自行谷歌:css 0.5像素,有N多的解决方案,这里不再赘述。 5.问:有时候字体会不受控制的变大,怎么办? 答:在X5新内核Blink中,在排版页面的时候,会主动对字体进行放大,会检测页面中的主字体,当某一块字体在我们的判定规则中,认为字号较小,并且是页面中的主要字体,就会采取主动放大的操作。然而这不是我们想要的,可以采取给最大高度解决 解决方案: , :before, :after { max-height: 100000px } 补充:有同学反映,在一些情况下 textarea 标签内的字体大小即便加上上面的方案,字体也会变大,无法控制。此时你需要给 textarea 的 display 设为 table 或者 inline-table 即可恢复正常。(感谢 程序媛喵喵 对此的补充!2017/7/7) 6.问:我在底部导航用的flex感觉更合适一些,请问这样子混着用可以吗? 答:咱们的rem适合写固定尺寸。其余的根据需要换成flex或者百分比。源码示例中就有这三种的综合运用。 7.问:在高清方案下,一个标准的,较为理想的宽度为640的页面效果图应该是怎样的? 点击浏览:一个标准的640手机页面设计稿参考(没错,在此方案中,你可以完全按照这张设计稿的尺寸写布局了。就是这么简单!) 8.问:用了这个方案如何使用媒体查询呢? 一般来讲,使用了这个方案是没必要用媒体查询了,如果你必须要用,假设你要对 iphone5 (css像素宽度320px, 这里需要取其物理像素,也就是640)宽度下的类名做处理,你可以这样 @media screen and (max-width: 640px) {.yourLayout {width:100%;} } 9.问:可以提供下这个高清方案的源码吗? 'use strict';/ @param {Boolean} [normal = false] - 默认开启页面压缩以使页面高清; @param {Number} [baseFontSize = 100] - 基础fontSize, 默认100px; @param {Number} [fontscale = 1] - 有的业务希望能放大一定比例的字体;/const win = window;export default win.flex = (normal, baseFontSize, fontscale) => {const _baseFontSize = baseFontSize || 100;const _fontscale = fontscale || 1;const doc = win.document;const ua = navigator.userAgent;const matches = ua.match(/Android[\S\s]+AppleWebkit\/(\d{3})/i);const UCversion = ua.match(/U3\/((\d+|\.){5,})/i);const isUCHd = UCversion && parseInt(UCversion[1].split('.').join(''), 10) >= 80;const isIos = navigator.appVersion.match(/(iphone|ipad|ipod)/gi);let dpr = win.devicePixelRatio || 1;if (!isIos && !(matches && matches[1] > 534) && !isUCHd) {// 如果非iOS, 非Android4.3以上, 非UC内核, 就不执行高清, dpr设为1;dpr = 1;}const scale = normal ? 1 : 1 / dpr;let metaEl = doc.querySelector('meta[name="viewport"]');if (!metaEl) {metaEl = doc.createElement('meta');metaEl.setAttribute('name', 'viewport');doc.head.appendChild(metaEl);}metaEl.setAttribute('content', width=device-width,user-scalable=no,initial-scale=${scale},maximum-scale=${scale},minimum-scale=${scale});doc.documentElement.style.fontSize = normal ? '50px' : ${_baseFontSize / 2 dpr _fontscale}px;}; 10.问:我在使用 rem 布局进阶方案的时候遇到了XXX的问题,如何解决? 此方案久经考验,具有普遍适用性,自身出致命问题的情况很少,至少笔者是没遇到过。 绝大多数你遇到的问题,都是由于对rem布局理解不到位导致的。本文对rem布局做了大量的解释说明,配置了若干 demo,你可以把你遇到的问题放到demo里测试。遇到问题时,首先问自己,为什么这明显的错误大家没遇到就我遇到了?? 如果你真的经过充分验证,比对,确实是rem布局自身出了问题,那么请私信我,把还原问题场景的 demo 或者文件发给我。谢谢! 本篇文章为转载内容。原文链接:https://blog.csdn.net/hjhfreshman/article/details/88864894。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-03-23 12:01:53
133
转载
Netty
...要聊聊一个超级厉害的技术——Netty。嘿,要是你对分布式系统、高能网络编程或者大数据流处理这些酷炫的东西感兴趣,那Netty可就太值得一试了!它就像是个隐藏的宝藏,能让你在这些领域玩得更溜。 首先,Netty是什么?简单来说,Netty是一个基于Java的异步事件驱动网络应用框架。它可以帮助开发者快速构建可扩展的服务器端应用程序。想象一下,你正在开发一个需要处理海量数据的大数据流处理平台,这时候Netty就显得尤为重要了。它不仅能够帮助我们高效地管理网络连接,还能让我们轻松应对高并发场景。 我第一次接触Netty的时候,真的被它的灵活性震撼到了。哎,说到程序员的烦心事,那肯定得提一提怎么让程序在被成千上万的人同时戳的时候还能稳如老狗啊!这事儿真心让人头大,尤其是看着服务器指标噌噌往上涨,心里直打鼓,生怕哪一秒就崩了。而Netty通过非阻塞I/O模型,完美解决了这个问题。这就像是一个超级能干的服务员,能够在同一时间同时服务上万个客人,而且就算有个客人纠结半天点菜(也就是某个请求拖拉),也不会耽误其他客人的服务,更不会让整个餐厅都停下来等他。 举个栗子: java EventLoopGroup bossGroup = new NioEventLoopGroup(); // 主线程组 EventLoopGroup workerGroup = new NioEventLoopGroup(); // 工作线程组 try { ServerBootstrap b = new ServerBootstrap(); // 启动辅助类 b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) // 使用NIO通道 .childHandler(new ChannelInitializer() { // 子处理器 @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new StringDecoder()); // 解码器 ch.pipeline().addLast(new StringEncoder()); // 编码器 ch.pipeline().addLast(new SimpleChannelInboundHandler() { @Override protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { System.out.println("Received message: " + msg); ctx.writeAndFlush("Echo: " + msg); // 回显消息 } }); } }); ChannelFuture f = b.bind(8080).sync(); // 绑定端口并同步等待完成 f.channel().closeFuture().sync(); // 等待服务关闭 } finally { workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); } 这段代码展示了如何用Netty创建一个简单的TCP服务器。话说回来,Netty这家伙简直太贴心了,它的API设计得特别直观,想设置啥处理器或者监听事件都超简单,用起来完全没压力,感觉开发效率直接拉满! 2. 大数据流处理平台中的挑战 接下来,我们聊聊大数据流处理平台面临的挑战。在这个领域,我们通常会遇到以下几个问题: - 高吞吐量:我们需要处理每秒数百万条甚至更多的数据记录。 - 低延迟:对于某些实时应用场景(如股票交易),毫秒级的延迟都是不可接受的。 - 可靠性:数据不能丢失,必须保证至少一次投递。 - 扩展性:随着业务增长,系统需要能够无缝扩容。 这些问题听起来是不是很让人头大?但别担心,Netty正是为此而生的! 让我分享一个小故事吧。嘿,有次我正忙着弄个日志收集系统,结果一测试才发现,这传统的阻塞式I/O模型简直是“人形瓶颈”啊!流量一大就直接崩溃,完全hold不住那个高峰时刻,简直让人头大!于是,我开始研究Netty,并将其引入到项目中。哈哈,结果怎么样?系统的性能直接翻了三倍!这下我可真服了,选对工具真的太重要了,感觉像是找到了开挂的装备一样爽。 为了更好地理解这些挑战,我们可以看看下面这段代码,这是Netty中用来实现高性能读写的示例: java public class HighThroughputHandler extends ChannelInboundHandlerAdapter { private final ByteBuf buffer; public HighThroughputHandler() { buffer = Unpooled.buffer(1024); } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { for (int i = 0; i < 1024; i++) { buffer.writeByte((byte) i); } ctx.writeAndFlush(buffer.retain()); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ctx.write(msg); } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { ctx.flush(); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } } 在这段代码中,我们创建了一个自定义的处理器HighThroughputHandler,它能够在每次接收到数据后立即转发出去,从而实现高吞吐量的传输。 3. Netty如何优化大数据流处理平台? 现在,让我们进入正题——Netty是如何具体优化大数据流处理平台的呢? 3.1 异步非阻塞I/O Netty的核心优势在于其异步非阻塞I/O模型。这就相当于,当有请求进来的时候,Netty可不会给每个连接都专门安排一个“服务员”,而是让这些连接共用一个“服务团队”。这样既能节省人手,又能高效处理各种任务,多划算啊!这样做的好处是显著减少了内存占用和上下文切换开销。 假设你的大数据流处理平台每天要处理数十亿条数据记录,采用传统的阻塞式I/O模型,很可能早就崩溃了。而Netty则可以通过单线程处理数千个连接,极大地提高了资源利用率。 3.2 零拷贝技术 另一个让Netty脱颖而出的特点是零拷贝技术。嘿,咱们就拿快递打个比方吧!想象一下,你在家里等着收快递,但这个快递特别麻烦——它得先从仓库(相当于内核空间)送到快递员手里(用户空间),然后快递员再把东西送回到你家(又回到内核空间)。这就像是数据在网络通信里来回折腾了好几趟,一会儿在系统深处待着,一会儿又被搬出来给应用用,真是费劲啊!这种操作不仅耗时,还会消耗大量CPU资源。 Netty通过ZeroCopy机制,直接将数据从文件系统传递到网络套接字,避免了不必要的内存拷贝。这种做法不仅加快了数据传输速度,还降低了系统的整体负载。 这里有一个实际的例子: java FileRegion region = new DefaultFileRegion(fileChannel, 0, fileSize); ctx.write(region); 上述代码展示了如何利用Netty的零拷贝功能发送大文件,无需手动加载整个文件到内存中。 3.3 灵活的消息编解码 在大数据流处理平台中,数据格式多种多样,可能包括JSON、Protobuf、Avro等。Netty提供了一套强大的消息编解码框架,允许开发者根据需求自由定制解码逻辑。 例如,如果你的数据是以Protobuf格式传输的,可以这样做: java public class ProtobufDecoder extends MessageToMessageDecoder { @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { byte[] data = new byte[in.readableBytes()]; in.readBytes(data); MyProtoMessage message = MyProtoMessage.parseFrom(data); out.add(message); } } 通过这种方式,我们可以轻松解析复杂的数据结构,同时保持代码的整洁性和可维护性。 3.4 容错与重试机制 最后但同样重要的是,Netty内置了强大的容错与重试机制。在网上聊天或者传输文件的时候,有时候会出现消息没发出去、对方迟迟收不到的情况,就像快递丢了或者送慢了。Netty这个小助手可机灵了,它会赶紧发现这些问题,然后试着帮咱们把没送到的消息重新发一遍,就像是给快递员多派一个人手,保证咱们的信息能安全顺利地到达目的地。 java RetryHandler retryHandler = new RetryHandler(maxRetries); ctx.pipeline().addFirst(retryHandler); 上面这段代码展示了如何添加一个重试处理器到Netty的管道中,让它在遇到错误时自动重试。 4. 总结与展望 经过这一番探讨,相信大家已经对Netty及其在大数据流处理平台中的应用有了更深入的理解。Netty可不只是个工具库啊,它更像是个靠谱的小伙伴,陪着咱们一起在高性能网络编程的大海里劈波斩浪、寻宝探险! 当然,Netty也有它的局限性。比如说啊,遇到那种超级复杂的业务场景,你可能就得绞尽脑汁写一堆专门定制的代码,不然根本搞不定。还有呢,这门技术的学习难度有点大,刚上手的小白很容易觉得晕头转向,不知道该怎么下手。但我相信,只要坚持实践,总有一天你会爱上它。 未来,随着5G、物联网等新技术的发展,大数据流处理的需求将会更加旺盛。而Netty凭借其卓越的性能和灵活性,必将在这一领域继续发光发热。所以,不妨大胆拥抱Netty吧,它会让你的开发之旅变得更加精彩! 好了,今天的分享就到这里啦!如果你有任何疑问或者想法,欢迎随时交流。记住,编程之路没有终点,只有不断前进的脚步。加油,朋友们!
2025-04-26 15:51:26
46
青山绿水
站内搜索
用于搜索本网站内部文章,支持栏目切换。
知识学习
实践的时候请根据实际情况谨慎操作。
随机学习一条linux命令:
uniq file.txt
- 移除连续重复行。
推荐内容
推荐本栏目内的其它文章,看看还有哪些文章让你感兴趣。
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
历史内容
快速导航到对应月份的历史文章列表。
随便看看
拉到页底了吧,随便看看还有哪些文章你可能感兴趣。
时光飞逝
"流光容易把人抛,红了樱桃,绿了芭蕉。"