首页 文章 精选 留言 我的

精选列表

搜索[初体验],共236篇文章
优秀的个人博客,低调大师

阿里云VMware无代理整机迁移服务初体验

当前对于很多企业客户,期待着把线下机房(服务器)搬迁上云,而传统的文件级别的迁云工具在虚拟化环境中操作又比较复杂,因此一款更加专业便捷的迁云服务便应运而生——阿里云VMware无代理整机迁移服务。 VMware整机迁移服务主要提供针对VMware环境的虚拟机提供非侵入式的无代理整机迁移功能,主要原理是基于VMware的快照以及磁盘级别的数据读取功能,将全盘的数据在ECS上云盘上进行重放。下面来试用一下这款服务,看看体验如何。 创建迁移网关: 首先,登录混合云备份(HBR)控制台,进入虚机迁移的页面,点击创建迁移网关: 输入网关的名字后即可完成创建,接下来点击下载客户端,会将客户端镜像Alibaba-Cloud-Migration-Appliance-*.ova下载到本地 部署网关: 登录本地的vCenter环境,部署OVA镜像(目前只支持通过vCenter的Web Client部署,并且未来VMware也只会支持Web Client,而不支持Native Client了): 激活网关: 网关开机后,访问http://[网关ip地址]:8011,进入激活页面后输入ak,以及上传证书(控制台下载网关处,或者在网关的操作中) 激活成功后,点击确定,会跳转到迁移的控制台: 添加vCenter: 在迁移控制台上,点击“查看”->“添加vCenter服务器”: 输入vCenter的地址,用户名和密码后,即可完成添加 进行迁移: 点击“迁移”按钮: 输入迁移的名称,并选择是否立即迁移,或者进行计划迁移: 然后在类似vCenter浏览目录的页面选择需要进行迁移的虚拟机: 最后便是选择迁移到ECS的配置: 全部配置完成后,点击创建,即可启动当前这个迁移任务。在迁移状态页面,我们便可以看到它: 确认迁移结果: 等迁移任务结束时,我们可以通过ECS的链接进入到ECS的详情页,登录并检查机器。 整体试用下来,VMware迁移服务在易用性上表现良好,并且不需要侵入待迁移服务器的系统内部,安全性有保障,如果有需要将VMware环境虚拟机迁移到阿里云的场景,用它是非常合适的。

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

初体验-阿里云短视频 SDK For Android 快速接入

前言 近期的一些创意短视频 App 风靡年轻群体,比较典型的例如抖音、MUSE 等,阿里云也适时地推出了简单易用的短视频 SDK,帮助开发者们以较低的成本快速引入功能完备的创意短视频功能。本文主要介绍如何快速接入阿里云短视频 SDK 的三个版本(基础版、标准版和专业版)。帮助开发者以最快的速度了解接入的基本方式。 本文描述的阿里云短视频 SDK 版本基于 3.4.0,后续升级接口变动请参考 阿里云短视频 SDK 文档。示例工程代码为 Kotlin,Java 接入类似。 正文 由于三个版本接入方式大同小异,本文将着重介绍基础版接入过程,标准版和专业版可以基于基础版方式接入,后续仅说明接入差异的地方。 基础版接入 1. 引入 aar 以及 so 目前 aar 平台版本最低要求 >= 4.3,先从SDK 下载页面下载相应版本的 SDK,解压之后,将 libs 文件夹下的 QuSdk-RC.aar 拷到 Android 工程模块中的 libs 文件夹下,将 jniLibs 文件夹下的 armeabi-v7a 文件夹也整体拷贝到 libs 文件夹下。拷贝完成之后目录的文件如下: 之后按照如下方式修改 Android 项目工程主模块下的 build.gradle 文件: Step1. 修改 jniLibs 的源文件夹; android { sourceSets.main { jniLibs.srcDir "libs" } } Step2. 将 libs 文件夹加入仓库中; repositories { flatDir { dirs 'libs' } } Step3. 增加 aar 所需依赖。 dependencies { implementation(name: 'QuSdk-RC', ext: 'aar') implementation 'com.android.support:appcompat-v7:24.2.1' implementation 'com.android.support:design:24.2.1' implementation 'com.google.code.findbugs:jsr305:3.0.0' implementation 'com.github.bumptech.glide:glide:3.7.0' implementation 'pub.devrel:easypermissions:0.2.1' implementation 'com.squareup.okhttp3:okhttp:3.2.0' implementation 'com.github.bumptech.glide:okhttp3-integration:1.4.0@aar' implementation 'com.squareup.okio:okio:1.12.0' implementation 'com.google.code.gson:gson:2.8.0' } 如果此处遭遇 java.lang.NoSuchFieldError 错误,可以参考短视频安卓常见问题解决。 2. 初始化 SDK 请根据具体的项目情况选择合适的 SDK 初始化时机,Demo 工程在 Applicatioin 的 onCreate() 方法中初始化。 package me.bogerchan.alishortvideodemo import android.app.Application import com.aliyun.common.httpfinal.QupaiHttpFinal /** * Created by hb.chen on 2018/1/6. */ class MyApplication : Application() { override fun onCreate() { super.onCreate() System.loadLibrary("QuCore-ThirdParty") System.loadLibrary("QuCore") QupaiHttpFinal.getInstance().initOkHttpFinal() } } 3. 开始书写你的业务逻辑 经过上述过程,实际上已经接入完成,这时候你可以参考文档直接开始使用各种 API 了,附下示例代码。 package me.bogerchan.alishortvideodemo import android.Manifest import android.app.Activity import android.content.Intent import android.content.pm.PackageManager import android.os.Bundle import android.support.v4.app.ActivityCompat import android.support.v7.app.AppCompatActivity import android.widget.Toast import com.aliyun.demo.recorder.AliyunVideoRecorder import com.aliyun.struct.common.VideoQuality import com.aliyun.struct.snap.AliyunSnapVideoParam import me.bogerchan.alishortvideodemo.basic.R class MainActivity : AppCompatActivity() { companion object { val REQUEST_CODE_RECORD_VIDEO = 1 val REQUEST_CODE_FOR_PERMISSION = 2 } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) findViewById(R.id.btn_start_record).setOnClickListener { startRecordActivity() } ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO), REQUEST_CODE_FOR_PERMISSION) } private fun startRecordActivity() { val recordParam = AliyunSnapVideoParam.Builder() .setResolutionMode(AliyunSnapVideoParam.RESOLUTION_720P) .setRatioMode(AliyunSnapVideoParam.RATIO_MODE_9_16) .setRecordMode(AliyunSnapVideoParam.RECORD_MODE_AUTO) .setNeedClip(true) .setMaxDuration(10000) .setMinDuration(2000) .setVideQuality(VideoQuality.HD) .setSortMode(AliyunSnapVideoParam.SORT_MODE_MERGE) .build() AliyunVideoRecorder.startRecordForResult(this, REQUEST_CODE_RECORD_VIDEO, recordParam) } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) when (requestCode) { REQUEST_CODE_RECORD_VIDEO -> { if (resultCode == Activity.RESULT_OK && data != null) { val type = data.getIntExtra(AliyunVideoRecorder.RESULT_TYPE, 0) if (type == AliyunVideoRecorder.RESULT_TYPE_CROP) { Toast.makeText(this, "类型为裁剪", Toast.LENGTH_SHORT).show() } else if (type == AliyunVideoRecorder.RESULT_TYPE_RECORD) { Toast.makeText(this, "文件路径为 " + data.getStringExtra(AliyunVideoRecorder.OUTPUT_PATH), Toast.LENGTH_SHORT).show() } } else if (resultCode == Activity.RESULT_CANCELED) { Toast.makeText(this, "用户取消录制", Toast.LENGTH_SHORT).show() } } } } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) when (requestCode) { REQUEST_CODE_FOR_PERMISSION -> { grantResults.forEach { if (it == PackageManager.PERMISSION_DENIED) { Toast.makeText(this, "没有权限,不玩了", Toast.LENGTH_SHORT).show() finish() return@forEach } } } } } } 标准版接入 1. 引入 aar 以及 so 标准版相较于基础版,在引入 so 文件时候多了几个文件,同时 aar 文件名有所变动。最终拷贝结果如下:build.gradle 文件修改与基础版接入一样,只是需要将接入 aar 文件名替换成标准版对应的名字。 2. 初始化 SDK 相较于基础版,需要加载的 so 增多了几个,其中部分 so 文件作为可选功能根据实际情况决定是否加载,具体可以参阅阿里云短视频 SDK 文档。接入后的 Application 文件参考: package me.bogerchan.alishortvideodemo import android.app.Application import com.aliyun.common.httpfinal.QupaiHttpFinal /** * Created by hb.chen on 2018/1/6. */ class MyApplication : Application() { override fun onCreate() { super.onCreate() System.loadLibrary("aliresample") System.loadLibrary("live-openh264") System.loadLibrary("QuCore-ThirdParty") System.loadLibrary("QuCore") QupaiHttpFinal.getInstance().initOkHttpFinal() } } 3. 开始书写你的业务逻辑 经过上述过程,实际上已经接入完成,这时候你可以参考文档直接开始使用各种 API 了,附下示例代码。 package me.bogerchan.alishortvideodemo import android.Manifest import android.content.pm.PackageManager import android.opengl.GLSurfaceView import android.os.Bundle import android.support.v4.app.ActivityCompat import android.support.v7.app.AppCompatActivity import android.widget.Toast import com.aliyun.recorder.AliyunRecorderCreator import com.aliyun.struct.recorder.CameraType import com.aliyun.struct.recorder.MediaInfo import me.bogerchan.alishortvideodemo.std.R class MainActivity : AppCompatActivity() { companion object { val REQUEST_CODE_FOR_PERMISSION = 1 } private val mRecorder by lazy { AliyunRecorderCreator.getRecorderInstance(this) } private var mCameraType = CameraType.FRONT override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO), REQUEST_CODE_FOR_PERMISSION) initAliyunRecorder() findViewById(R.id.btn_start_record).setOnClickListener { Toast.makeText(this, "开始录制片段", Toast.LENGTH_SHORT).show() mRecorder.startRecording() } findViewById(R.id.btn_stop_record).setOnClickListener { Toast.makeText(this, "停止录制片段", Toast.LENGTH_SHORT).show() mRecorder.stopRecording() } findViewById(R.id.btn_finish_record).setOnClickListener { Toast.makeText(this, "结束录制", Toast.LENGTH_SHORT).show() mRecorder.finishRecording() } findViewById(R.id.btn_change_camera_type).setOnClickListener { Toast.makeText(this, "切换前后置", Toast.LENGTH_SHORT).show() mRecorder.switchCamera() } } override fun onStart() { super.onStart() mRecorder.startPreview() } override fun onPause() { super.onPause() mRecorder.stopPreview() } override fun onDestroy() { super.onDestroy() AliyunRecorderCreator.destroyRecorderInstance() } private fun initAliyunRecorder() { mRecorder.setDisplayView(findViewById(R.id.glsv_content) as GLSurfaceView) val mediaInfo = MediaInfo() mediaInfo.videoWidth = 800 mediaInfo.videoHeight = 1200 mediaInfo.isHWAutoSize = true mRecorder.setMediaInfo(mediaInfo) mRecorder.setCamera(mCameraType) mRecorder.setOutputPath(externalCacheDir.absolutePath + "/capture.mp4") } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) when (requestCode) { REQUEST_CODE_FOR_PERMISSION -> { grantResults.forEach { if (it == PackageManager.PERMISSION_DENIED) { Toast.makeText(this, "没有权限,不玩了", Toast.LENGTH_SHORT).show() finish() return@forEach } } } } } } 专业版接入 1. 引入 aar 以及 so 专业版相较于基础版,在引入 so 文件时候多了几个文件,同时 aar 文件名有所变动。最终拷贝结果如下: build.gradle 文件修改与基础版接入一样,只是需要将接入 aar 文件名替换成专业版对应的名字。 2. 初始化 SDK 相较于基础版,需要加载的 so 增多了几个,其中部分 so 文件作为可选功能根据实际情况决定是否加载,具体可以参阅阿里云短视频 SDK 文档。接入后的 Application 文件参考: package me.bogerchan.alishortvideodemo import android.app.Application import com.aliyun.common.httpfinal.QupaiHttpFinal /** * Created by hb.chen on 2018/1/6. */ class MyApplication : Application() { override fun onCreate() { super.onCreate() System.loadLibrary("live-openh264") System.loadLibrary("QuCore-ThirdParty") System.loadLibrary("QuCore") System.loadLibrary("FaceAREngine") System.loadLibrary("AliFaceAREngine") QupaiHttpFinal.getInstance().initOkHttpFinal() } } 3. 开始书写你的业务逻辑 经过上述过程,实际上已经接入完成,这时候你可以参考文档直接开始使用各种 API 了,附下示例代码。 package me.bogerchan.alishortvideodemo import android.Manifest import android.content.pm.PackageManager import android.opengl.GLSurfaceView import android.os.Bundle import android.support.v4.app.ActivityCompat import android.support.v7.app.AppCompatActivity import android.widget.Toast import com.aliyun.recorder.AliyunRecorderCreator import com.aliyun.struct.recorder.CameraType import com.aliyun.struct.recorder.MediaInfo import me.bogerchan.alishortvideodemo.pro.R class MainActivity : AppCompatActivity() { companion object { val REQUEST_CODE_FOR_PERMISSION = 1 } private val mRecorder by lazy { AliyunRecorderCreator.getRecorderInstance(this) } private var mCameraType = CameraType.FRONT override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO), REQUEST_CODE_FOR_PERMISSION) initAliyunRecorder() findViewById(R.id.btn_start_record).setOnClickListener { Toast.makeText(this, "开始录制片段", Toast.LENGTH_SHORT).show() mRecorder.startRecording() } findViewById(R.id.btn_stop_record).setOnClickListener { Toast.makeText(this, "停止录制片段", Toast.LENGTH_SHORT).show() mRecorder.stopRecording() } findViewById(R.id.btn_finish_record).setOnClickListener { Toast.makeText(this, "结束录制", Toast.LENGTH_SHORT).show() mRecorder.finishRecording() } findViewById(R.id.btn_change_camera_type).setOnClickListener { Toast.makeText(this, "切换前后置", Toast.LENGTH_SHORT).show() mRecorder.switchCamera() } } override fun onStart() { super.onStart() mRecorder.startPreview() } override fun onPause() { super.onPause() mRecorder.stopPreview() } override fun onDestroy() { super.onDestroy() AliyunRecorderCreator.destroyRecorderInstance() } private fun initAliyunRecorder() { mRecorder.setDisplayView(findViewById(R.id.glsv_content) as GLSurfaceView) val mediaInfo = MediaInfo() mediaInfo.videoWidth = 800 mediaInfo.videoHeight = 1200 mediaInfo.isHWAutoSize = true mRecorder.setMediaInfo(mediaInfo) mRecorder.setCamera(mCameraType) mRecorder.needFaceTrackInternal(true) mRecorder.setOutputPath(externalCacheDir.absolutePath + "/capture.mp4") } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) when (requestCode) { REQUEST_CODE_FOR_PERMISSION -> { grantResults.forEach { if (it == PackageManager.PERMISSION_DENIED) { Toast.makeText(this, "没有权限,不玩了", Toast.LENGTH_SHORT).show() finish() return@forEach } } } } } } 结语 至此已经介绍完了阿里云短视频 SDK 的接入方法,示例代码展示的仅仅只是阿里云视频 SDK 强大功能的冰山一角,开发者们可以通过相关的 SDK 文档获取更多的接口信息。如果集成过程中遇到问题,在联系客服之前不妨先看下 常见问题解决,说不定你的问题就在里面。

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

ASP.NET MVC 3 Beta初体验之WebGrid

ASP.NET MVC 3 Beta中除了推出一种新的视图引擎Razor。还推出了几种新的HtmlHelper。我比较关注的是WebGrid,这篇文章将介绍一下WebGrid的使用。WebGrid提供了分页和排序的功能,在此之前在MVC中分页和排序时需要自己去写的。这篇文章将分别介绍在aspx视图引擎和Razor视图引擎中如何使用它。 我通过ADO.NET Entity Data Model从NORTHWND的Products中表中取数据。在Controller中取数据: public class HomeController : Controller { public ActionResult Index() { NORTHWNDEntities entity = new NORTHWNDEntities(); return View( entity.Products.ToList()); } } 在aspx视图引擎中使用WebGrid代码如下: < div id ="grid" > <% var grid = new WebGrid(source: Model, defaultSort: " ProductName " , rowsPerPage: 5 ); %> <% = grid.GetHtml( tableStyle: " grid " , headerStyle: " head " , alternatingRowStyle: " alt " , columns: grid.Columns( grid.Column(format: (item) => Html.ActionLink( " Edit " , " Edit " , new { id = item.ProductID })), grid.Column(format: (item) => Html.ActionLink( " Delete " , " Delete " , null , new { onclick = string .Format( " deleteRecord('Employee', '{0}') " , item.ProductID), @class = " Delete " , href = " JavaScript:void(0) " })), grid.Column( " ProductName " , " 产品名称 " ), grid.Column( " QuantityPerUnit " , " 每单位数量 " ), grid.Column( " UnitPrice " , " 单价 " ), grid.Column( " UnitsInStock " , " 库存单位 " ), grid.Column( " UnitsOnOrder " , " 订单单位 " ), grid.Column( " ReorderLevel " , " 重新排序级别 " ), grid.Column( " Discontinued " , " 已停产 " ) ) ) %> </ div > 在Razor中使用WebGrid代码如下: < div id ="grid" > @{ var grid = new WebGrid(source: Model, defaultSort: "ProductName", rowsPerPage: 10); } @grid.GetHtml( tableStyle: "grid", headerStyle: "head", alternatingRowStyle: "alt", columns: grid.Columns( grid.Column(format: (item) => Html.ActionLink("Edit", "Edit", new { id = item.ProductID })), grid.Column(format: (item) => Html.ActionLink("Delete", "Delete", null, new { onclick = string.Format("deleteRecord('Product', '{0}')", item.ProductID), @class = "Delete", href = "JavaScript:void(0)" })), grid.Column("ProductName","产品名称"), grid.Column("QuantityPerUnit","每单位数量"), grid.Column("UnitPrice","单价"), grid.Column("UnitsInStock", "库存单位"), grid.Column("UnitsOnOrder","订单单位"), grid.Column("ReorderLevel","重新排序级别"), grid.Column("Discontinued","已停产") ) ) </ div > WebGrid构造函数如下: public WebGrid(IEnumerable<dynamic> source, IEnumerable<string> columnNames = null, string defaultSort = null, int rowsPerPage = 10, bool canPage = true, bool canSort = true, string ajaxUpdateContainerId = null, string fieldNamePrefix = null, string pageFieldName = null, string selectionFieldName = null, string sortFieldName = null, string sortDirectionFieldName = null); 常见参数意思是: 1、source 表示数据源 2、columnNames表示显示的列 3、defaultSort 默认按什么排序 4、rowsPerPage 每页多少行数据 5、canPage 是否能排序 上面两段代码的意思是定义了一个既分页又能排序的grid。 运行: 在看看两个view的完整代码: aspx: <% @ Page Title = "" Language = " C# " MasterPageFile = " ~/Views/Shared/Site.Master " Inherits = " System.Web.Mvc.ViewPage<List<WebGridAspx.Models.Products>> " %> < asp:Content ID = " Content1 " ContentPlaceHolderID = " TitleContent " runat = " server " > 产品列表 </ asp:Content > < asp:Content ID = " Content2 " ContentPlaceHolderID = " MainContent " runat = " server " > < script type = " text/javascript " > function deleteRecord(a, b) { alert( " 删除: " + b); } </ script > < h2 > 产品列表 </ h2 > < div id = " grid " > <% var grid = new WebGrid(source: Model, defaultSort: " ProductName " , rowsPerPage: 5 ); %> <%= grid.GetHtml( tableStyle: " grid " , headerStyle: " head " , alternatingRowStyle: " alt " , columns: grid.Columns( grid.Column(format: (item) => Html.ActionLink( " Edit " , " Edit " , new { id = item.ProductID })), grid.Column(format: (item) => Html.ActionLink( " Delete " , " Delete " , null , new { onclick = string .Format( " deleteRecord('Employee', '{0}') " , item.ProductID), @class = " Delete " , href = " JavaScript:void(0) " })), grid.Column( " ProductName " , " 产品名称 " ), grid.Column( " QuantityPerUnit " , " 每单位数量 " ), grid.Column( " UnitPrice " , " 单价 " ), grid.Column( " UnitsInStock " , " 库存单位 " ), grid.Column( " UnitsOnOrder " , " 订单单位 " ), grid.Column( " ReorderLevel " , " 重新排序级别 " ), grid.Column( " Discontinued " , " 已停产 " ) ) ) %> </ div > </ asp:Content > Razor: 代码 @model List < WebGridRazor.Models.Products > @{ View.Title = " 产品列表 " ; } < p > < h2 > 产品列表 </ h2 > < div id = " grid " > @{ var grid = new WebGrid(source: Model, defaultSort: " ProductName " , rowsPerPage: 3 ); } @grid.GetHtml( tableStyle: " grid " , headerStyle: " head " , alternatingRowStyle: " alt " , columns: grid.Columns( grid.Column(format: (item) => Html.ActionLink( " Edit " , " Edit " , new { id = item.ProductID })), grid.Column(format: (item) => Html.ActionLink( " Delete " , " Delete " , null , new { onclick = string .Format( " deleteRecord('Product', '{0}') " , item.ProductID), @class = " Delete " , href = " JavaScript:void(0) " })), grid.Column( " ProductName " , " 产品名称 " ), grid.Column( " QuantityPerUnit " , " 每单位数量 " ), grid.Column( " UnitPrice " , " 单价 " ), grid.Column( " UnitsInStock " , " 库存单位 " ), grid.Column( " UnitsOnOrder " , " 订单单位 " ), grid.Column( " ReorderLevel " , " 重新排序级别 " ), grid.Column( " Discontinued " , " 已停产 " ) ) ) </ div > </ p > Razor去掉了那些模板页的代码,使代码看起来更整洁。比较喜欢Razor。 总结:本文很简单,介绍了一下ASP.NET MVC 3 Beta中新功能WebGrid,由于这种方式WebGrid是在内存中分页和排序的,所以不适合大数据量。 代码:http://files.cnblogs.com/zhuqil/MvcApplication11.rar 本文转自麒麟博客园博客,原文链接:http://www.cnblogs.com/zhuqil/archive/2010/10/17/ASP-NET-MVC-3-Beta-WebGrid.html,如需转载请自行联系原作者

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

自动化接口回归测试神器 AREX 使用初体验

AREX 是一款开源的基于流量录制回放技术的自动化回归测试平台,目前官方文档仅介绍了如何在本地及私有云部署,本篇文章分享如何 AWS 环境下快速搭建 AREX 服务,并使用 AWS 的 DocumentDB 作为数据库替换官方默认的 MongoDB,使用 ElastiCache 替换默认的 Redis。 安装 AREX 使用前需要注册 AWS 账号并对相应概念有一定了解,详细可参考 AWS 官方文档。 步骤一:准备一台 EC2 用于部署 AREX 操作系统选择 Amazon Linux 2 AMI。(Amazon Linux 2023 AMI 有部分应用安装不上,所以这里选择使用稳定的低版本操作系统) 如果只是进行试用,建议最小配置选择 t3.large(2C8G)的机器。 密钥对名称可以按照自己的需求创建并使用(方便快速链接到自己的 EC2)。后续创建 DocumentDB 的时候也可以共用该秘钥。 存储配置默认 8G 即可。 步骤二:创建 Amazon DocumentDB 集群 注意:在使用 DocumentDB 时,需要将其部署在与之关联的 EC2 实例所在的同一个虚拟私有云(VPC)中,保证它们之间能够进行通信。 创建 2 个 db.t3.medium 类型的实例,1 主 1 从两台机器,引擎版本选择 5.0.0。 安装完成后,配置对应的入站规则以允许外部网络通过连接串进行访问。使用 mongo shell 验证连接串是否可用。 执行 show dbs 命令,出现上图中红框部分则表示 DocumentDB 创建成功。 步骤四:准备一台 ElastiCache 并创建 Redis 集群 在 AWS 控制台中搜索 ElastiCache 并创建 Redis 集群。注意:ElastiCache 必须与上述创建的 EC2 在同一个 VPC 中。 选择配置并创建新集群,试用阶段可以先禁用集群模式,正式使用时按需修改配置。 引擎版本选择 6.2,节点类型选择 cache.t3.micro,副本数量设置为 0。 选择创建新的子网组,选择和上述 EC2 同一个 VPC ID。 在 EC2 上通过 redis-cli 连接到 ElastiCache 检查是否连通。如果连接不上可以看下安全组对应的入站规则,根据自己网络情况进行配置即可。 步骤五:通过 docker-compose 安装 AREX AREX 的安装非常简单,使用 Docker-Compose 命令,即可一键安装 AREX 所有基础服务组件。 这里简单介绍一下 AREX 的工作原理及各个服务组件。 AREX 回归测试的工作原理是利用 AREX Java Agent 将生产环境中 Java 应用的数据流量和请求信息进行采样录制,并将这些信息发送给 AREX 数据存取服务(Storage Service),由数据存取服务导入数据库(mongoDB)中进行存储。当需要进行回放时,AREX 调度服务(Schedule Service)将会通过 Storage Service 从数据库中提取被测应用的录制数据,然后向目标验证服务发送接口请求。同时,Java Agent 会将录制的外部依赖的响应进行 Mock,代替真正的数据访问,传达给被测应用,目标服务处理完成请求逻辑后返回响应报文。随后调度服务会将录制的响应报文与回放的响应报文进行比对,验证系统逻辑正确性,并将比对结果推送给分析服务(Report Service),由其生成完整的回放测试报告,供测试人员分析录制回放差异。 首先,通过 git 命令克隆 AREX 仓库。 git clone --depth 1 https://github.com/arextest/deployments.git cd deployments 配置 DocumentDB、ElastiCache 如要使用 AWS 的 DocumentDB 作为数据库替换官方默认的 MongoDB,并使用 ElastiCache 替换默认的 Redis,只需修改配置文件 docker-compose.yml 中的连接串,把文件中所有 MongoDB 的连接串都替换成 DocumentDB 的连接串,所有 Redis 的连接串都替换成 ElastiCache 的连接串即可。 步骤六:启动 AREX 配置完成后,执行 docker-compose 一键启动 AREX 服务。 docker-compose up -d 服务启动后,在没有修改端口配置的情况下,直接访问 8088 端口进入 AREX 前端页面。 差异分析 在实际使用过程中,对于一个复杂的线上应用,业务场景复杂,录制及回放的用例数量巨大,如何分析差异点及排查问题成为难点。 为了减轻使用者分析差异时的工作量,AREX 对可能存在的大量差异点,使用聚合的方法进行了大幅度的简化。 差异场景聚合分析 首先是差异场景聚合,AREX 对差异场景相同的多个用例进行了聚合展示。在介绍差异场景聚合逻辑之前,先大概了解一下 AREX 中用例(Case)的基本概念。 AREX 用例概念 在 AREX 中,一个用例通常由多个步骤组成,每个步骤包含了一个请求和一个响应。请求可以是主入口,也可以是外部调用(包括 DB、Redis 等)。在每个步骤中,都会记录请求的参数和响应结果等信息,用于后续的对比。如果录制与回放时的主入口响应,以及外部依赖的请求均无差异,则视为该用例回放通过。 这里的主入口和外部调用我们称之为 Mock 的类型。 Mock 的差异类型 每个 Mock 类型的对比差异类型会被分为三种情况: new call:这种差异类型表示该主入口或外部调用的 Mock 在录制时不存在,但在回放时存在,即新增了调用,通常是因为有新功能的迭代。 call missing:表示在录制时存在,但在回放时缺失了调用,通常是因为项目进行了优化,移除了某些不必要的调用关系。 value diff:表示在录制和回放时都存在,但在对比过程中某些节点有差异。后续章节中会具体介绍如何分析这些差异。 差异场景聚合 在进行流量回放测试时,针对可能出现的用例数量较大的情况,AREX 会通过一些聚合的操作,将相似的用例进行合并,以减少差异点的数量,便于用户对数据进行分析。如下图所示,聚合相似差异场景后,每个差异场景下仅选取一条用例作为展示。 场景聚合逻辑 有了上述 Mock 差异类型的概念,接下来介绍下差异用例场景聚合的逻辑。差异用例场景聚合是为了将具有相同 Mock 类型和差异类型的用例聚合在一起形成一个场景,从而帮助用户更快速地了解整个场景中的用例情况,减少用户需要分析的用例数量,提高分析用例的效率。 首先,根据 Mock 的 type 类型和差异类型的组合,AREX 会生成一个唯一的键,将所有用例分类聚合到这些键中,形成一个大分类。如上图中标注的 ①大分类。 其次,每个大分类中的用例都会再根据具体的 Mock 和差异类型的排列生成一个子唯一键,进一步对用例进行分类。这样做的目的是为了更加细致地分类,以便更快速地分析差异用例,具体可见上图中 ②小分类 的示例。每个小分类中有多少个用例数量会标记在该分类的最前面。 差异点分析 其次在每个差异场景中,AREX 对相似的差异节点也进行了聚合展示。 差异点聚合逻辑 在某些大报文的场景下,有些大数组中的差异点会非常多,一方面不利于前端展示,另一方面增加了使用者分析差异点的复杂度。 为了解决这个问题,AREX 将差异点按照模糊路径进行聚合。这里的模糊路径指的是不带数组下标的 JSON 节点路径。例如,一个 JSON 对象中有一个名为 “items” 的数组,数组中有多个元素 “items[0]”、“items[1]”、“items[2]” 等。在模糊路径中,这些路径会被合并为 “items”,从而实现聚合。 新增节点 在比较两个 JSON 对象时,如果发现某一边 JSON 对象中存在一个节点,但是另一边的对象中不存在该节点,那么我们就认为该节点是“新增节点”,如下图中,仅出现在右侧对象中的 cardpayfee 节点。同时为了方便用户查看,AREX 会将新增节点的所有祖先节点都标注为褐色,以突出显示该节点的位置。这样,用户可以清晰地看到该节点在另一侧对象中的位置,从而更好地分析问题。 差异节点 如果左右两边对象都存在某节点,但节点的值不一致,就可以认为该节点是“差异节点”,AREX 会使用蓝色背景高亮显示这些节点。不同于新增节点,这种类型的差异点只会出现在叶子节点上,因为只有叶子节点才有具体的值可以进行比较。 AREX 文档:http://arextest.com/zh-Hans/docs/intro/ AREX 官网:http://arextest.com/ AREX GitHub:https://github.com/arextest AREX 官方 QQ 交流群:656108079

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

PPOCRLabel 半自动化图形标注工具初体验

Windows + Anaconda 下载安装Anaconda(Python 3+) conda install pyqt=5 cd ./PPOCRLabel # 将目录切换到PPOCRLabel文件夹下 pyrcc5 -o libs/resources.py resources.qrc python PPOCRLabel.py 按官方文档执行完上述命令 报错1:is 和 is not不能识别,手动替换对应py文件中的 is 为‘==’ is not 替换为 ‘!=’ 报错2: 明明已经安装了,还是报这个错!因为是非专业的python小白,折腾了很久,最后直接 将anaconda3\Lib\site-packages下对应包复制到PPOCRLabel目录下即可!后续还报了个lxml模块找不到的错误,也是同样的解决方法! 效果图如下所示: 下载安装命令 ## CPU版本安装命令 pip install -f https://paddlepaddle.org.cn/pip/oschina/cpu paddlepaddle ## GPU版本安装命令 pip install -f https://paddlepaddle.org.cn/pip/oschina/gpu paddlepaddle-gpu >>访问 PaddlePaddle 官网,了解更多相关内容。

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

一位 Rust 开发者的 Go 初体验

作者介绍:Nick Cameron,PingCAP 研发工程师,Rust 语言核心成员。 感谢 Rust 语言中文社区伙伴们的翻译和审校: 翻译:尚卓燃 审校:吴聪、张汉东 过去几周,我一直在用 Go 语言编写程序。这是我首次在大型且重要的项目中使用 Go。在研究 Rust 的特性时,我也看了很多关于 Go 的内容,包括体验示例和编写玩具程序。但真正用它编程又是一种完全不同的体验。 我觉得把这次体验写下来应该会很有趣。在这篇文章中,我会尽量避免将 Go 与 Rust 进行过多的比较,不过,由于我是从 Rust 转向 Go,难免也会包含一些比较。应该事先声明的是,我更偏袒 Rust ,但会尽力做到客观。 总体印象 用 Go 编程的感觉很棒。库程序里有我想要的一切,总体实现较为完善。学习体验也十分顺畅,不得不说,Go 是一种经过精心设计的实用性语言。举个例子:一旦你知悉了 Go 的语法,就能将其他语言中惯用法延续到 Go 中。只要你学会一些 Go,就可以相对轻易地推测 Go 语言的其他特性。凭借一些来自其他语言的知识,我能够阅读并理解 Go 代码,而不需要过多的搜索(Google)。 与 C/C++、Java、Python 等相比,Go 并没有那么多痛点,而且更具生产力。然而,它还是与这些语言处在同一个时代。尽管它从其他语言身上吸取了一些教训,甚至我个人认为它可能是那一代语言中最好的那个,但绝对还属于那一代语言。这是一种渐进式的改进,而不是推陈出新(需要明确的是,这不是意味着对其价值的批判,从软件工程的角度,渐进式改进通常会带来好的影响)。一个很好的例证是 nil:像 Rust 和 Swift 这样的语言已经去除了 null 的概念,并且消除了相关的一整类错误。Go 降低了一部分风险:没有空值(no null values),在 nil 和 0 之间进行区分。但其核心思想仍未改变,同样还会出现解空指针引用这种常见的运行时错误。 易学性 Go 非常易学。我知道人们经常吹捧这一点,但是我真的为自己生产力的飞速提高而感到震惊。多亏了 Go 语言以及它的文档和工具,我仅仅花了两天时间就可以写出「有价值」、可以提交的代码。 有助于易学性的几个因素是: Go 很精简。很多语言都试图让自己看起来小巧,但 Go 真正做到了这一点(这基本上是一件好事,我对这种自律精神印象深刻)。 标准库很出色(同样,也很小)。从生态系统中寻找并使用库程序非常容易。 几乎没有其他语言中所不具备的东西。Go 从其他既存语言中提取了很多内容,并进行完善,最后将它们很好地组合在一起。它在避免标新立异这一方面做了极大努力。 乏味的样板式代码 Go 代码很快就会变得非常重复。这是由于它缺乏宏或者泛型这种用于减少重复的机制(接口虽然有利于抽象,但在减少代码重复方面作用没有那么大)。最终我会写很多函数,而他们除了类型不同之外其他甚至完全一样。 错误处理也会导致重复。许多函数中像 if err != nil { return err } 这样的样板式代码甚至比那些真正有价值的代码还要多。 使用泛型或宏来减少样板式代码有时会受到批评,理由是不应为使代码易于编写而使其丧失可读性。我发现 Go 恰恰提供了一个反例,复制和粘贴代码往往既快速又简单,阅读代码却会令人灰心丧气,因为你不得不忽略大量的无关代码或者在大量的相同代码中找到细微的不同。 我喜欢的东西 编译时间:绝对快,可以确定要比 Rust 快得多。但实际上,它并没有我预期的那么快(对于中型到大型项目,我感觉它的速度只是与 C/C++ 相接近,或者稍微快一点。而我更加期待能够即时编译)。 协程(goroutine)和信道(channel):值得称赞的是,Go 为生成协程和使用信道提供了轻量级的语法。尽管只是一个小细节,却使 Go 的并发编程体验比其他语言更优越,它真正揭示了语法的力量。 接口:它们并不复杂,但是很容易理解和使用,并且在很多地方都很实用。 if ...; ... { } 语法:可以将变量的作用域限制在 if 语句真的很好。这与 Swift 及 Rust 中的 if let 起着相似的效果,但用途更为广泛(Go 没有像 Swift 和 Rust 那样的模式匹配,所以它无法使用 if let )。 测试和文档注释都很容易使用。 Go 工具链非常友好:将所有东西都放在一个地方,而不需要在命令行上使用多个工具。 拥有垃圾收集器(GC):不用考虑内存管理真的会使编程更加轻松。 可变参数。 我不喜欢的东西 以下内容没有特定的顺序。 nil 切片:要知道 nil、nil 切片和空切片三者都不相同,我敢保证我们只需要其中的两个,而不需要第三个。 枚举类型并不是第一公民:使用常量模拟枚举让人感觉是一种倒退。 不允许循环引用:这实际上限制了包在划分项目模块中的可用性,因为它变相鼓励了在一个包中堆积大量文件(或拥有大量零碎的小包,如果本该放在一起的文件四处分散,这也同样糟糕)。 switch 允许出现遗漏匹配的情况。 for ... range 语句会返回一对「索引/值」。要想只获取索引很容易(忽略值就好);但若要只获取值,则需要显式声明。在我看来,这种做法更应该颠倒过来,因为在大多数情况下,我更需要值而不是索引。 语法: 定义与用途存在不一致。 编译器有时会很挑剔(例如,要求或禁止尾随逗号);通过良好的工具可以缓解这种困扰,但是有时仍然会产生一些恼人的额外步骤。 使用多值返回类型时,类型上需要括号,但 return 语句中却不需要。 声明一个结构体需要两个关键字(type 和 struct)。 采用大写命名法来标记公共或私有变量,看起来就像匈牙利命名法那样,但更糟糕。 隐式接口。我知道它也出现在我喜欢的东西中,但有时候它确实很惹人烦——特别是当你试图找出所有实现该接口的类型,或者哪些接口是为给定类型而实现的时候。 你无法在不同的包中编写带有接收器的函数,所以即使接口是「鸭子类型」的,你也不能为其他包中的类型实现这个接口,这使得它们的用处大大降低。 还有我之前已经提过的,Go 缺少泛型和宏。 一致性 作为一名语言设计者和程序员,Go 最让我惊讶的地方也许是它的内置功能和用户可用功能之间频频出现不一致。许多语言的目标之一就是尽可能消除编译器魔法,让用户也能使用内置功能。运算符重载是一个简单但有争议的例子。但 Go 有很多魔法!你很容易就会遇到这样的问题:无法做那些内置功能可以做的事情。 一些让我印象深刻的地方: 返回多个值和信道的语法很棒,但是这两个无法一起使用,因为没有元组类型。 能够用 for ... range 语句对数组和切片进行迭代,但对其他集合就无能为力了,因为它缺乏迭代器的概念。 像 len 或者 append 这样的函数是全局函数,但你自己的函数却无法转变成全局函数。这些全局函数只能使用内置类型。即便 Go「没有泛型」,它们也可以变得通用。 没有运算符重载,那么 == 就会使人感到恼火。因为这意味着你不能在词典中使用自定义类型作为键,除非它们是可比较的。这一属性派生自类型结构,程序员无法重写该属性。 总结 Go 是一种简单、小巧、令人愉悦的语言。它也有一些犄角旮旯,但绝大部分是经过精心设计的。它的学习速度令人难以置信,并且规避了其他语言中一些不那么广为人知的特性。 Go 也是一种与 Rust 截然不同的语言。虽然两者都可以笼统地描述为「系统语言」或「C 语言的替代品」,但它们的设计目标、应用领域、语言风格和优先级不尽相同。垃圾收集确实带来了一个巨大的差异:使用 GC 使得 Go 变得更简单、更小,也更容易理解。而不使用 GC 使 Rust 奇快无比(特别是在您需要保证延迟,而不仅仅是高吞吐量的时候),并且得以支持 Go 中不可能实现的特性或编程模式(或者至少在不牺牲性能的前提下是无法实现的)。 Go 是一种编译型语言,其运行时得到了良好的实现,其速度毋庸置疑。Rust 也是编译型语言,但是运行时要小得多,它真的迅捷无比。在没有其他限制的情况下,我认为选择使用 Go 还是 Rust 其实意味着一种权衡: 一方面,Go 的学习曲线更短、程序更简单(这意味着更快的开发速度); 另一方面,Rust 真的性能卓越,并且类型系统更富有表现力(这使程序更安全,也意味着更快的调试和错误查找)。 附:英文原版文章 欢迎大家在下方留言,说说你的 Go & Rust 体验吧:从入门到放弃,从放弃到精通,踩过的坑,流过的泪……或者说说它们的必杀优势。 ⚠️理智探讨,Peace & Love.

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

Hadoop初体验:快速搭建Hadoop伪分布式环境

本文旨在使用一个全新安装好的Linux系统从0开始进行Hadoop伪分布式环境的搭建,以达到快速搭建的目的,从而体验Hadoop的魅力所在,为后面的继续学习提供基础环境。 对使用的系统环境作如下说明: 操作系统:CentOS 6.5 64位 主机IP地址:10.0.0.131/24 主机名:leaf 用户名:root hadoop版本:2.6.5 jdk版本:1.7 可以看到,这里直接使用root用户,而不是按照大多数的教程创建一个hadoop用户来进行操作,就是为了达到快速搭建Hadoop环境以进行体验的目的。 为了保证后面的操作能够正常完成,请先确认本机是否可以解析到主机名leaf,如果不能,请手动添加解析到/etc/hosts目录中: 1 2 3 4 5 6 [root@leaf~] #echo"127.0.0.1leaf">>/etc/hosts [root@leaf~] #pingleaf PINGleaf(127.0.0.1)56(84)bytesofdata. 64bytesfromlocalhost(127.0.0.1):icmp_seq=1ttl=64 time =0.043ms 64bytesfromlocalhost(127.0.0.1):icmp_seq=2ttl=64 time =0.048ms 64bytesfromlocalhost(127.0.0.1):icmp_seq=3ttl=64 time =0.046ms 1.rsync软件安装 使用下面命令安装: 1 [root@leaf~] #yuminstall-yrsync 2.ssh安装与免密码登陆配置 (1)ssh安装 使用下面命令安装 1 [root@leaf~] #yuminstall-yopenssh-serveropenssh-clients (2)ssh免密码登陆配置 因为Hadoop使用ssh协议来管理远程守护进程,所以需要配置免密码登陆。 关闭防火墙和selinux 为了确保能够成功配置,在配置前,先把防火墙和selinux关闭: 1 2 3 4 5 6 7 8 9 10 #关闭防火墙 [root@leaf~] #/etc/init.d/iptablesstop [root@leaf~] #chkconfig--level3iptablesoff #关闭selinux [root@leaf~] #setenforce0 [root@leaf~] #sed-is/SELINUX=enforcing/SELINUX=disabled/g/etc/selinux/config [root@leaf~] #cat/etc/selinux/config|grepdisabled #disabled-NoSELinuxpolicyisloaded. SELINUX=disabled 生成密钥对 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 [root@leaf~] #mkdir.ssh [root@leaf~] #ssh-keygen-tdsa-P''-f.ssh/id_dsa Generatingpublic /private dsakeypair. Youridentificationhasbeensaved in . ssh /id_dsa . Yourpublickeyhasbeensaved in . ssh /id_dsa .pub. Thekeyfingerprintis: 5b:af:7c:45:f3:ff: dc :50:f5:81:4b:1e:5c:c1:86:90root@leaf Thekey'srandomartimageis: +--[DSA1024]----+ |.ooo.| |E..oo| |=...| |o=+| |S.+oo| |o....| |.....| |...oo| |o.=| +-----------------+ 将公钥添加到本地信任列表 1 [root@leaf~] #cat.ssh/id_dsa.pub>>.ssh/authorized_keys 验证 上面三步完成后就完成了免密码登陆的配置,可以使用下面的命令进行验证: 1 2 3 4 5 6 7 [root@leaf~] #sshlocalhost Theauthenticityofhost 'localhost(::1)' can'tbeestablished. RSAkeyfingerprintisd1:0d:ed:eb:e7:d1:2f:02:23:70:ef:11:14:4e:fa:42. Areyousureyouwantto continue connecting( yes /no )? yes Warning:Permanentlyadded 'localhost' (RSA)tothelistofknownhosts. Lastlogin:WedAug3004:28:012017from10.0.0.1 [root@leaf~] # 在第一次登陆的时候需要输入yes,之后再登陆时就可以直接登陆了: 1 2 3 [root@leaf~] #sshlocalhost Lastlogin:WedAug3004:44:022017fromlocalhost [root@leaf~] # 3.jdk安装与配置 (1)jdk下载 这里使用的是jdk1.7版本,可以到下面的网站进行下载: http://www.oracle.com/technetwork/java/javase/downloads/java-archive-downloads-javase7-521261.html 下载完成后,可以使用winscp上传到/root目录下,如下: 1 2 [root@leaf~] #ls-lhjdk-7u80-linux-x64.tar.gz -rw-r--r--.1rootroot147MAug2912:05jdk-7u80-linux-x64. tar .gz (2)jdk安装 将jdk解压到/usr/local目录下,并创建软链接: 1 2 3 4 5 6 7 8 [root@leaf~] #cpjdk-7u80-linux-x64.tar.gz/usr/local/ [root@leaf~] #cd/usr/local/ [root@leaf local ] #tar-zxfjdk-7u80-linux-x64.tar.gz [root@leaf local ] #ls-ldjdk1.7.0_80/ drwxr-xr-x.8uucp1434096Apr112015jdk1.7.0_80/ [root@leaf local ] #ln-sjdk1.7.0_80/jdk [root@leaf local ] #ls-ldjdk lrwxrwxrwx.1rootroot12Aug3004:56jdk->jdk1.7.0_80/ (3)JAVA_HOME环境变量配置 java命令在/usr/local/jdk/bin目录下: 1 2 3 [root@leaf local ] #cdjdk/bin/ [root@leafbin] #ls-lhjava -rwxr-xr-x.1uucp1437.6KApr112015java 配置java环境变量: 1 2 3 [root@leafbin] #echo'exportJAVA_HOME=/usr/local/jdk/bin'>>/etc/profile [root@leafbin] #echo'exportPATH=$PATH:$JAVA_HOME'>>/etc/profile [root@leafbin] #source/etc/profile 这样我们就可以在任何一个目录下使用java相关的命令: 1 2 3 4 5 6 [root@leaf~] #java-version javaversion "1.7.0_80" Java(TM)SERuntimeEnvironment(build1.7.0_80-b15) JavaHotSpot(TM)64-BitServerVM(build24.80-b11,mixedmode) [root@leaf~] #javac-version javac1.7.0_80 4.hadoop安装与配置 (1)hadoop下载 这里使用hadoop 2.6.5版本,可以到下面的网站进行下载: http://hadoop.apache.org/releases.html 选择2.6.5的binary进入相应的页面便可以下载,然后使用winscp上传到/root目录下,如下: 1 2 [root@leaf~] #ls-lhhadoop-2.6.5.tar.gz -rw-r--r--.1rootroot191MAug2919:09hadoop-2.6.5. tar .gz (2)hadoop安装 将hadoop解压到/usr/local目录下,并创建软链接: 1 2 3 4 5 6 7 8 [root@leaf~] #cphadoop-2.6.5.tar.gz/usr/local [root@leaf~] #cd/usr/local [root@leaf local ] #tar-zxfhadoop-2.6.5.tar.gz [root@leaf local ] #ls-ldhadoop-2.6.5 drwxrwxr-x.9100010004096Oct32016hadoop-2.6.5 [root@leaf local ] #ln-shadoop-2.6.5hadoop [root@leaf local ] #ls-ldhadoop lrwxrwxrwx.1rootroot12Aug3005:05hadoop->hadoop-2.6.5 (3)hadoop环境变量配置 hadoop相关命令在/usr/local/hadoop/bin和/usr/local/hadoop/sbin目录下,如下所示: 1 2 3 [root@leaf local ] #cdhadoop/bin/ [root@leafbin] #ls-lhhadoop -rwxr-xr-x.1100010005.4KOct32016hadoop 配置hadoop环境变量: 1 2 [root@leafbin] #echo'exportHADOOP_HOME=/usr/local/hadoop/bin:/usr/local/hadoop/sbin'>>/etc/profile [root@leafbin] #echo'exportPATH=$PATH:$HADOOP_HOME'>>/etc/profile 这样我们就可以在任何一个目录下使用hadoop相关的命令: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 [root@leaf~] #hadoop Usage:hadoop[--configconfdir]COMMAND whereCOMMANDisoneof: fsrunagenericfilesystemuserclient versionprinttheversion jar<jar>runajar file checknative[-a|-h]checknativehadoopandcompressionlibrariesavailability distcp<srcurl><desturl>copy file ordirectoriesrecursively archive-archiveNameNAME-p<parentpath><src>*<dest>createahadooparchive classpathprintstheclasspathneededtogetthe credentialinteractwithcredentialproviders Hadoopjarandtherequiredlibraries daemonlogget /set theloglevel for eachdaemon traceviewandmodifyHadooptracingsettings or CLASSNAMEruntheclassnamedCLASSNAME Mostcommandsprinthelpwheninvokedw /o parameters. (4)hadoop配置 hadoop的配置文件在/usr/local/hadoop/etc/hadoop目录下: 1 2 3 4 5 6 7 8 9 10 [root@leaf~] #cd/usr/local/hadoop/etc/hadoop/ [root@leafhadoop] #ls capacity-scheduler.xmlhadoop-policy.xmlkms-log4j.propertiesssl-client.xml.example configuration.xslhdfs-site.xmlkms-site.xmlssl-server.xml.example container-executor.cfghttpfs- env .shlog4j.propertiesyarn- env .cmd core-site.xmlhttpfs-log4j.propertiesmapred- env .cmdyarn- env .sh hadoop- env .cmdhttpfs-signature.secretmapred- env .shyarn-site.xml hadoop- env .shhttpfs-site.xmlmapred-queues.xml.template hadoop-metrics2.propertieskms-acls.xmlmapred-site.xml.template hadoop-metrics.propertieskms- env .shslaves 配置core-site.xml 1 2 3 4 5 6 <configuration> <property> <name>fs.default.name< /name > <value>hdfs: //localhost :9000< /value > < /property > < /configuration > fs.default.name这个字段下的值用于指定NameNode(HDFS的Master)的IP地址和端口号,如下面的value值hdfs://localhost:9000,就表示HDFS NameNode的IP地址或主机为localhost,端口号为9000. 配置hdfs-site.xml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 <configuration> <property> <name>dfs.replication< /name > <value>1< /value > < /property > <property> <name>dfs.name. dir < /name > <value> /root/hdfs-filesystem/name < /value > < /property > <property> <name>dfs.data. dir < /name > <value> /root/hdfs-filesystem/data < /value > < /property > < /configuration > dfs.replication用于指定HDFS中每个Block块被复制的次数,起到数据冗余备份的作用;dfs.name.dir用于配置HDFS的NameNode的元数据,以逗号隔开,HDFS会把元数据冗余复制到这些目录下;dfs.data.dir用于配置HDFS的DataNode的数据目录,以逗号隔开,HDFS会把数据存在这些目录下。 配置mapred-site.xml 1 2 3 4 5 6 <configuration> <property> <name>mapred.job.tracker< /name > <value>localhost:9001< /value > < /property > < /configuration > mapred.job.tracker字段用于指定MapReduce Jobtracker的IP地址及端口号,如这里IP地址或主机为localhost,9001是MapReduce Jobtracker RPC的交互端口。 配置hadoop-env.sh 1 export JAVA_HOME= /usr/local/jdk 5.hadoop启动与测试 (1)格式化HDFS分布式文件系统 执行如下命令: 1 2 3 4 5 6 7 8 [root@leaf~] #hadoopnamenode-format ... 17 /08/30 08:41:29INFOnamenode.NNStorageRetentionManager:Goingtoretain1imageswithtxid>=0 17 /08/30 08:41:29INFOutil.ExitUtil:Exitingwithstatus0 17 /08/30 08:41:29INFOnamenode.NameNode:SHUTDOWN_MSG: /************************************************************ SHUTDOWN_MSG:ShuttingdownNameNodeatleaf /127 .0.0.1 ************************************************************/ 注意看输出显示是不是跟上面的类似,如果是,则说明操作成功。 (2)启动hadoop服务 执行如下命令: 1 2 3 4 5 6 7 8 9 10 11 12 [root@leaf~] #start-all.sh ThisscriptisDeprecated.Insteadusestart-dfs.shandstart-yarn.sh 17 /08/30 08:53:22WARNutil.NativeCodeLoader:Unabletoloadnative-hadooplibrary for yourplatform...using builtin -javaclasseswhereapplicable Startingnamenodeson[localhost] localhost:startingnamenode,loggingto /usr/local/hadoop-2 .6.5 /logs/hadoop-root-namenode-leaf .out localhost:startingdatanode,loggingto /usr/local/hadoop-2 .6.5 /logs/hadoop-root-datanode-leaf .out Startingsecondarynamenodes[0.0.0.0] 0.0.0.0:startingsecondarynamenode,loggingto /usr/local/hadoop-2 .6.5 /logs/hadoop-root-secondarynamenode-leaf .out 17 /08/30 08:53:48WARNutil.NativeCodeLoader:Unabletoloadnative-hadooplibrary for yourplatform...using builtin -javaclasseswhereapplicable startingyarndaemons startingresourcemanager,loggingto /usr/local/hadoop-2 .6.5 /logs/yarn-root-resourcemanager-leaf .out localhost:startingnodemanager,loggingto /usr/local/hadoop-2 .6.5 /logs/yarn-root-nodemanager-leaf .out (3)hadoop服务测试 启动完成后,执行jps命令,可以看到hadoop运行的守护进程,如下: 1 2 3 4 5 6 7 [root@leaf~] #jps 4167SecondaryNameNode 4708Jps 3907NameNode 4394NodeManager 4306ResourceManager 3993DataNode 也可以通过在浏览器中输入地址来访问相关页面,这里访问NameNode的页面,地址为http://10.0.0.131:50070,如下: 访问DataNode的页面,地址为http://10.0.0.131:50075,如下 本文转自 xpleaf 51CTO博客,原文链接:http://blog.51cto.com/xpleaf/1960982,如需转载请自行联系原作者

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

阿里云Kubernetes服务上使用Tekton完成应用发布初体验

Tekton 是一个功能强大且灵活的 Kubernetes 原生开源框架,用于创建持续集成和交付(CI/CD)系统。通过抽象底层实现细节,用户可以跨多云平台和本地系统进行构建、测试和部署。 本文是基于阿里云Kubernetes服务部署Tekton Pipeline,并使用它完成源码拉取、应用打包、镜像推送和应用部署的实践过程。 Tekton Pipeline中有5类对象,核心理念是通过定义yaml定义构建过程.构建任务的状态存放在status字段中。 其中5类对象分别是:PipelineResouce、Task、TaskRun、Pipeline、PipelineRun。 Task是单个任务的构建过程,需要通过定义TaskRun任务去运行Task。 Pipeline包含多个Task,并在此基础上定义input和output,input和out

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

阿里云Kubernetes Service Mesh实践进行时(1): Istio初体验

概述 [Announcement] Istio第一个生产可用版本1.0于2018年7月31日正式发布。 注意:在使用阿里云Kubernetes容器服务Istio 1.0的过程中,如果遇到类似CRD版本问题,请参考我们提供的问题分析。 我们会持续更新遇到的问题及其解决方法。 Istio是一个用于连接/管理以及安全化微服务的开放平台,提供了一种简单的方式用于创建微服务网络,并提供负载均衡、服务间认证以及监控等能力,并且关键的一点是并不需要修改服务本身就可以实现上述功能。 Istio主要提供以下功能: 流量管理: 控制服务之间调用的流量和API调用; 使得调用更可靠,并使网络在恶劣情况下更加健壮; 可观察性: 获取服务之间的依赖,以及服务调用的流量走向; 从而提供快速识别问题的能力; 策略执行:控制服务的访问策略,不需要改动服务本身; 服务身份和安全:

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

文盘Rust -- tonic-Rust grpc初体验 | 京东云技术团队

gRPC 是开发中常用的开源高性能远程过程调用(RPC)框架,tonic 是基于 HTTP/2 的 gRPC 实现,专注于高性能、互操作性和灵活性。该库的创建是为了对 async/await 提供一流的支持,并充当用 Rust 编写的生产系统的核心构建块。今天我们聊聊通过使用tonic 调用grpc的的具体过程。 工程规划 rpc程序一般包含server端和client端,为了方便我们把两个程序打包到一个工程里面 新建tonic_sample工程 cargo new tonic_sample Cargo.toml 如下 [package] name = "tonic_sample" version = "0.1.0" edition = "2021" [[bin]] # Bin to run the gRPC server name = "stream-server" path = "src/stream_server.rs" [[bin]] # Bin to run the gRPC client name = "stream-client" path = "src/stream_client.rs" [dependencies] tokio.workspace = true tonic = "0.9" tonic-reflection = "0.9.2" prost = "0.11" tokio-stream = "0.1" async-stream = "0.2" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" rand = "0.7" h2 = { version = "0.3" } anyhow = "1.0.75" futures-util = "0.3.28" [build-dependencies] tonic-build = "0.9" tonic 的示例代码还是比较齐全的,本次我们参考 tonic 的 streaming example。 首先编写 proto 文件,用来描述报文。 proto/echo.proto syntax = "proto3"; package stream; // EchoRequest is the request for echo. message EchoRequest { string message = 1; } // EchoResponse is the response for echo. message EchoResponse { string message = 1; } // Echo is the echo service. service Echo { // UnaryEcho is unary echo. rpc UnaryEcho(EchoRequest) returns (EchoResponse) {} // ServerStreamingEcho is server side streaming. rpc ServerStreamingEcho(EchoRequest) returns (stream EchoResponse) {} // ClientStreamingEcho is client side streaming. rpc ClientStreamingEcho(stream EchoRequest) returns (EchoResponse) {} // BidirectionalStreamingEcho is bidi streaming. rpc BidirectionalStreamingEcho(stream EchoRequest) returns (stream EchoResponse) {} } 文件并不复杂,只有两个 message 一个请求一个返回,之所以选择这个示例是因为该示例包含了rpc中的流式处理,包扩了server 流、client 流以及双向流的操作。 编辑build.rs 文件 use std::{env, path::PathBuf}; fn main() -> Result<(), Box<dyn std::error::Error>> { tonic_build::compile_protos("proto/echo.proto")?; Ok(()) } 该文件用来通过 tonic-build 生成 grpc 的 rust 基础代码 完成上述工作后就可以构建 server 和 client 代码了 stream_server.rs pub mod pb { tonic::include_proto!("stream"); } use anyhow::Result; use futures_util::FutureExt; use pb::{EchoRequest, EchoResponse}; use std::{ error::Error, io::ErrorKind, net::{SocketAddr, ToSocketAddrs}, pin::Pin, thread, time::Duration, }; use tokio::{ net::TcpListener, sync::{ mpsc, oneshot::{self, Receiver, Sender}, Mutex, }, task::{self, JoinHandle}, }; use tokio_stream::{ wrappers::{ReceiverStream, TcpListenerStream}, Stream, StreamExt, }; use tonic::{transport::Server, Request, Response, Status, Streaming}; type EchoResult<T> = Result<Response<T>, Status>; type ResponseStream = Pin<Box<dyn Stream<Item = Result<EchoResponse, Status>> + Send>>; fn match_for_io_error(err_status: &Status) -> Option<&std::io::Error> { let mut err: &(dyn Error + 'static) = err_status; loop { if let Some(io_err) = err.downcast_ref::<std::io::Error>() { return Some(io_err); } // h2::Error do not expose std::io::Error with `source()` // https://github.com/hyperium/h2/pull/462 if let Some(h2_err) = err.downcast_ref::<h2::Error>() { if let Some(io_err) = h2_err.get_io() { return Some(io_err); } } err = match err.source() { Some(err) => err, None => return None, }; } } #[derive(Debug)] pub struct EchoServer {} #[tonic::async_trait] impl pb::echo_server::Echo for EchoServer { async fn unary_echo(&self, req: Request<EchoRequest>) -> EchoResult<EchoResponse> { let req_str = req.into_inner().message; let response = EchoResponse { message: req_str }; Ok(Response::new(response)) } type ServerStreamingEchoStream = ResponseStream; async fn server_streaming_echo( &self, req: Request<EchoRequest>, ) -> EchoResult<Self::ServerStreamingEchoStream> { println!("EchoServer::server_streaming_echo"); println!("\tclient connected from: {:?}", req.remote_addr()); // creating infinite stream with requested message let repeat = std::iter::repeat(EchoResponse { message: req.into_inner().message, }); let mut stream = Box::pin(tokio_stream::iter(repeat).throttle(Duration::from_millis(200))); let (tx, rx) = mpsc::channel(128); tokio::spawn(async move { while let Some(item) = stream.next().await { match tx.send(Result::<_, Status>::Ok(item)).await { Ok(_) => { // item (server response) was queued to be send to client } Err(_item) => { // output_stream was build from rx and both are dropped break; } } } println!("\tclient disconnected"); }); let output_stream = ReceiverStream::new(rx); Ok(Response::new( Box::pin(output_stream) as Self::ServerStreamingEchoStream )) } async fn client_streaming_echo( &self, _: Request<Streaming<EchoRequest>>, ) -> EchoResult<EchoResponse> { Err(Status::unimplemented("not implemented")) } type BidirectionalStreamingEchoStream = ResponseStream; async fn bidirectional_streaming_echo( &self, req: Request<Streaming<EchoRequest>>, ) -> EchoResult<Self::BidirectionalStreamingEchoStream> { println!("EchoServer::bidirectional_streaming_echo"); let mut in_stream = req.into_inner(); let (tx, rx) = mpsc::channel(128); tokio::spawn(async move { while let Some(result) = in_stream.next().await { match result { Ok(v) => tx .send(Ok(EchoResponse { message: v.message })) .await .expect("working rx"), Err(err) => { if let Some(io_err) = match_for_io_error(&err) { if io_err.kind() == ErrorKind::BrokenPipe { eprintln!("\tclient disconnected: broken pipe"); break; } } match tx.send(Err(err)).await { Ok(_) => (), Err(_err) => break, // response was droped } } } } println!("\tstream ended"); }); // echo just write the same data that was received let out_stream = ReceiverStream::new(rx); Ok(Response::new( Box::pin(out_stream) as Self::BidirectionalStreamingEchoStream )) } } #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { // 基础server let server = EchoServer {}; Server::builder() .add_service(pb::echo_server::EchoServer::new(server)) .serve("0.0.0.0:50051".to_socket_addrs().unwrap().next().unwrap()) .await .unwrap(); Ok(()) } server 端的代码还是比较清晰的,首先通过 tonic::include_proto! 宏引入grpc定义,参数是 proto 文件中定义的 package 。我们重点说说 server_streaming_echo function 。这个function 的处理流程明白了,其他的流式处理大同小异。首先 通过std::iter::repeat function 定义一个迭代器;然后构建 tokio_stream 在本示例中 每 200毫秒产生一个 repeat;最后构建一个 channel ,tx 用来发送从stream中获取的内容太,rx 封装到response 中返回。 最后 main 函数 拉起服务。 client 代码如下 pub mod pb { tonic::include_proto!("stream"); } use std::time::Duration; use tokio_stream::{Stream, StreamExt}; use tonic::transport::Channel; use pb::{echo_client::EchoClient, EchoRequest}; fn echo_requests_iter() -> impl Stream<Item = EchoRequest> { tokio_stream::iter(1..usize::MAX).map(|i| EchoRequest { message: format!("msg {:02}", i), }) } async fn unary_echo(client: &mut EchoClient<Channel>, num: usize) { for i in 0..num { let req = tonic::Request::new(EchoRequest { message: "msg".to_string() + &i.to_string(), }); let resp = client.unary_echo(req).await.unwrap(); println!("resp:{}", resp.into_inner().message); } } async fn streaming_echo(client: &mut EchoClient<Channel>, num: usize) { let stream = client .server_streaming_echo(EchoRequest { message: "foo".into(), }) .await .unwrap() .into_inner(); // stream is infinite - take just 5 elements and then disconnect let mut stream = stream.take(num); while let Some(item) = stream.next().await { println!("\treceived: {}", item.unwrap().message); } // stream is droped here and the disconnect info is send to server } async fn bidirectional_streaming_echo(client: &mut EchoClient<Channel>, num: usize) { let in_stream = echo_requests_iter().take(num); let response = client .bidirectional_streaming_echo(in_stream) .await .unwrap(); let mut resp_stream = response.into_inner(); while let Some(received) = resp_stream.next().await { let received = received.unwrap(); println!("\treceived message: `{}`", received.message); } } async fn bidirectional_streaming_echo_throttle(client: &mut EchoClient<Channel>, dur: Duration) { let in_stream = echo_requests_iter().throttle(dur); let response = client .bidirectional_streaming_echo(in_stream) .await .unwrap(); let mut resp_stream = response.into_inner(); while let Some(received) = resp_stream.next().await { let received = received.unwrap(); println!("\treceived message: `{}`", received.message); } } #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { let mut client = EchoClient::connect("http://127.0.0.1:50051").await.unwrap(); println!("Unary echo:"); unary_echo(&mut client, 10).await; tokio::time::sleep(Duration::from_secs(1)).await; println!("Streaming echo:"); streaming_echo(&mut client, 5).await; tokio::time::sleep(Duration::from_secs(1)).await; //do not mess server println functions // Echo stream that sends 17 requests then graceful end that connection println!("\r\nBidirectional stream echo:"); bidirectional_streaming_echo(&mut client, 17).await; // Echo stream that sends up to `usize::MAX` requests. One request each 2s. // Exiting client with CTRL+C demonstrate how to distinguish broken pipe from // graceful client disconnection (above example) on the server side. println!("\r\nBidirectional stream echo (kill client with CTLR+C):"); bidirectional_streaming_echo_throttle(&mut client, Duration::from_secs(2)).await; Ok(()) } 测试一下,分别运行 server 和 client cargo run --bin stream-server cargo run --bin stream-client 在开发中,我们通常不会再 client 和 server都开发好的情况下才开始测试。通常在开发server 端的时候采用 grpcurl 工具进行测试工作 grpcurl -import-path ./proto -proto echo.proto list grpcurl -import-path ./proto -proto echo.proto describe stream.Echo grpcurl -plaintext -import-path ./proto -proto echo.proto -d '{"message":"1234"}' 127.0.0.1:50051 stream.Echo/UnaryEcho 此时,如果我们不指定 -import-path 参数,执行如下命令 grpcurl -plaintext 127.0.0.1:50051 list 会出现如下报错信息 Failed to list services: server does not support the reflection API 让服务端程序支持 reflection API 首先改造build.rs use std::{env, path::PathBuf}; fn main() -> Result<(), Box<dyn std::error::Error>> { let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); tonic_build::configure() .file_descriptor_set_path(out_dir.join("stream_descriptor.bin")) .compile(&["proto/echo.proto"], &["proto"]) .unwrap(); Ok(()) } file_descriptor_set_path 生成一个文件,其中包含为协议缓冲模块编码的 prost_types::FileDescriptorSet 文件。这是实现 gRPC 服务器反射所必需的。 接下来改造一下 stream-server.rs,涉及两处更改。 新增 STREAM_DESCRIPTOR_SET 常量 pub mod pb { tonic::include_proto!("stream"); pub const STREAM_DESCRIPTOR_SET: &[u8] = tonic::include_file_descriptor_set!("stream_descriptor"); } 修改main函数 #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { // 基础server // let server = EchoServer {}; // Server::builder() // .add_service(pb::echo_server::EchoServer::new(server)) // .serve("0.0.0.0:50051".to_socket_addrs().unwrap().next().unwrap()) // .await // .unwrap(); // tonic_reflection let service = tonic_reflection::server::Builder::configure() .register_encoded_file_descriptor_set(pb::STREAM_DESCRIPTOR_SET) .with_service_name("stream.Echo") .build() .unwrap(); let addr = "0.0.0.0:50051".parse().unwrap(); let server = EchoServer {}; Server::builder() .add_service(service) .add_service(pb::echo_server::EchoServer::new(server)) .serve(addr) .await?; Ok(()) } register_encoded_file_descriptor_set 将包含编码的 prost_types::FileDescriptorSet 的 byte slice 注册到 gRPC Reflection 服务生成器注册。 再次测试 grpcurl -plaintext 127.0.0.1:50051 list grpcurl -plaintext 127.0.0.1:50051 describe stream.Echo 返回正确结果。 以上完整代码地址 作者:京东科技 贾世闻 来源:京东云开发者社区 转载请注明来源

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

【ChatGPT应用篇】助力Beauty代码的初体验 | 京东云技术团队

思考过程: 案例1:项目里面有Excel文件的解析场景,试着与ChatGPT进行了交互,现将问题整理如下: 1.给我写一个Java版本的excel导入解析代码 (毫不客气的分配任务) 2.需要支持100000000数据量 (业务需求变更) 3.优化代码需要支持10000000数据量 (降低数量级,减轻难度) 4.请采用面向对象的思想给做一下封装 (初级工程师 -> 中级工程师) 5.进一步面向接口编程,抽离业务 (中级晋升应该加一点泛型诉求,代码更Beauty) 6.替换掉 poi 采用EasyExcel (替换原始的默认技术选型,替换三方包) 7.进一步优化,能支持EasyExcel、POI自由切换 (问题没问好!本意是想让他进一步抽象,能基于SPI支持扩展点) 8.采用控制反转方式优化 (与问题5有重复) 9.提升解析性能,采用多线程并行解析优化 (中级工程->高级工程师) 10.提升程序效率,把解析性能在提升10倍 (架构师的成本意识,这个问题的回答有点意思) 11.采用Spring IOC 控制反转方式进一步封装 (与问题8有重复) 12.添加异常 logger 打印 (优化细节) 13.添加更多的中文代码注释 (增强易读性,优化细节) 14.将中文注释替换成英文注释 (^_^ 开始走向国际化) 15.日志错误信息支持 国际化 资源文件 (国际化的开始……) 16.把上面Excel解析的代码在给来个单元测试吧 17.… 案例2:项目里面一个ES深度分页优化的场景,问题整理如下: 1.Elasticsearch 深度分页问题 2.Elasticsearch 深度分页Java代码 3.以上代码采用面向IOC优化 4.进一步面向对象优化 5.基于泛型进行抽象 6.将 scroll API 替换为 searchafter 7.使用 spring data elasticsearch 优化并实现 search After 8.以上代码请采用jmeter 生成一个压测脚本 9.上面Spring Data Elasticsearch 实现 search after 的示例代码请采用 groovy 语言生成测试用例 案例2的代码就不贴出来了,大家感兴趣可以自行输入以上问题进行体验! (个人体会:程序领域问问题要采用专业术语,专业术语越强越能精准给予反馈!) 说明: 对于ChatGPT输出的代码没有做仔细的Review及调试,但是看代码风格基本上是改改就能用于生产的,对于代码规范、命名还是很认可的! 案例过程: Happy Coding!Beauty Code! 以下是一个与ChatGPT 结对编程的一个Excel数据解析案例,大家可以根据我的思考过程进行体验,以下放了一些截图,ChatGPT的反应速度惊人,代码版本一步步进行迭代优化,感触较深的是真正的高质量低代码平台要应运而生了! import com.alibaba.excel.EasyExcel; import com.alibaba.excel.annotation.ExcelProperty; import com.alibaba.excel.metadata.Sheet; import com.alibaba.excel.read.listener.ReadListener; import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet as PoiSheet; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class ExcelParser { private ParserType parserType; public ExcelParser(ParserType parserType) { this.parserType = parserType; } public List<Map<String, String>> parse(File file, int sheetIndex) throws IOException { if (parserType == ParserType.EASY_EXCEL) { return parseWithEasyExcel(file, sheetIndex); } else if (parserType == ParserType.POI) { return parseWithPoi(file, sheetIndex); } else { throw new IllegalArgumentException("Invalid parser type"); } } private List<Map<String, String>> parseWithEasyExcel(File file, int sheetIndex) { List<Map<String, String>> data = new ArrayList<>(); DataListener listener = new DataListener(data); ExcelReaderBuilder readerBuilder = EasyExcel.read(file, listener); Sheet sheet = new Sheet(sheetIndex + 1, 0); readerBuilder.sheet(sheet).doRead(); return data; } private List<Map<String, String>> parseWithPoi(File file, int sheetIndex) throws IOException { List<Map<String, String>> data = new ArrayList<>(); FileInputStream inputStream = new FileInputStream(file); HSSFWorkbook workbook = file.getName().endsWith(".xls") ? new HSSFWorkbook(inputStream) : new XSSFWorkbook(inputStream); PoiSheet sheet = workbook.getSheetAt(sheetIndex); List<String> headers = new ArrayList<>(); for (Cell cell : sheet.getRow(0)) { headers.add(cell.getStringCellValue()); } for (int i = 1; i <= sheet.getLastRowNum(); i++) { Row row = sheet.getRow(i); Map<String, String> record = new HashMap<>(); for (int j = 0; j < headers.size(); j++) { Cell cell = row.getCell(j, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK); String value = ""; switch (cell.getCellType()) { case STRING: value = cell.getStringCellValue(); break; case NUMERIC: value = Double.toString(cell.getNumericCellValue()); break; default: value = cell.getStringCellValue(); break; } record.put(headers.get(j), value); } data.add(record); } workbook.close(); inputStream.close(); return data; } public static void main(String[] args) throws IOException { File file = new File("excel_file.xlsx"); ExcelParser parser = new ExcelParser(ParserType.EASY_EXCEL); List<Map<String, String>> data = parser.parse(file, 0); System.out.println(data); } } enum ParserType { EASY_EXCEL, POI } class DataListener extends ReadListener<Data> { private List<Map<String, String>> data; private List<String> headers; public DataListener(List<Map<String, String>> data) { this.data = data; this.headers = new ArrayList<>(); } @Override public void onException(Exception exception) { // 处理异常 } @Override public void onReadError(Exception exception) { // 处理读取错误 } @Override public void onSheet(int sheetIndex, Sheet sheet) { // 处理工作表信息 } @Override public void onReadEnd() { // 读取结束 } @Override public void onHeadRow(Row row) { for (Cell cell : row) { headers.add(cell.getStringCellValue()); } } @Override public void onRecord(Data data) { Map<String, String> record = new HashMap<>(); for (int i = 0; i < headers.size(); i++) { record.put(headers.get(i), data.getField(i)); } this.data.add(record); } } class Data { @ExcelProperty(index = 0) private String name; @ExcelProperty(index = 1) private int age; import com.alibaba.excel.annotation.ExcelProperty; import java.util.List; import java.util.Map; public class ExcelParser { private SheetParser sheetParser; public ExcelParser(SheetParser sheetParser) { this.sheetParser = sheetParser; } public List<Map<String, String>> parse(String fileName, int sheetIndex) throws Exception { return sheetParser.parse(fileName, sheetIndex); } public static void main(String[] args) throws Exception { String fileName = "excel_file.xlsx"; SheetParser sheetParser = new EasyExcelSheetParser(); ExcelParser parser = new ExcelParser(sheetParser); List<Map<String, String>> data = parser.parse(fileName, 0); System.out.println(data); } } interface SheetParser { List<Map<String, String>> parse(String fileName, int sheetIndex) throws Exception; } class EasyExcelSheetParser implements SheetParser { @Override public List<Map<String, String>> parse(String fileName, int sheetIndex) throws Exception { List<Map<String, String>> data = new ArrayList<>(); DataListener listener = new DataListener(data); ExcelReaderBuilder readerBuilder = EasyExcel.read(fileName, listener); Sheet sheet = new Sheet(sheetIndex + 1, 0); readerBuilder.sheet(sheet).doRead(); return data; } } class PoiSheetParser implements SheetParser { @Override public List<Map<String, String>> parse(String fileName, int sheetIndex) throws Exception { List<Map<String, String>> data = new ArrayList<>(); FileInputStream inputStream = new FileInputStream(fileName); HSSFWorkbook workbook = fileName.endsWith(".xls") ? new HSSFWorkbook(inputStream) : new XSSFWorkbook(inputStream); PoiSheet sheet = workbook.getSheetAt(sheetIndex); List<String> headers = new ArrayList<>(); for (Cell cell : sheet.getRow(0)) { headers.add(cell.getStringCellValue()); } for (int i = 1; i <= sheet.getLastRowNum(); i++) { Row row = sheet.getRow(i); Map<String, String> record = new HashMap<>(); for (int j = 0; j < headers.size(); j++) { Cell cell = row.getCell(j, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK); String value = ""; switch (cell.getCellType()) { case STRING: value = cell.getStringCellValue(); break; case NUMERIC: value = Double.toString(cell.getNumericCellValue()); break; default: value = cell.getStringCellValue(); break; } record.put(headers.get(j), value); } data.add(record); } workbook.close(); inputStream.close(); return data; } } class DataListener extends ReadListener<Data> { private List<Map<String, String>> data; private List<String> headers; public DataListener(List<Map<String, String>> data) { this.data = data; this.headers = new ArrayList<>(); } @Override public void onException(Exception exception) { // 处理异常 } @Override public void onReadError(Exception exception) { // 处理读取错误 } @Override public void onSheet(int sheetIndex, Sheet sheet) { // 处理工作表信息 } @Override public void onReadEnd() { // 读取结束 } @Override public void onHeadRow(Row row) { for (Cell cell : row) { headers.add(cell.getStringCellValue()); } } @Override public void onRecord(Data data) { Map<String, String> record = new HashMap<>(); for (int i = 0; i < headers.size(); i++) { record.put(headers.get(i), data.getField(i)); } this.data.add(record); } } class Data { @ExcelProperty(index = 0) private String name; @ExcelProperty(index = 1) private int age; // Getters and setters } import com.alibaba.excel.EasyExcel; import com.alibaba.excel.annotation.ExcelProperty; import com.alibaba.excel.metadata.Sheet; import com.alibaba.excel.read.listener.ReadListener; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.*; public class ExcelParser { private SheetParser sheetParser; public ExcelParser(SheetParser sheetParser) { this.sheetParser = sheetParser; } public List<Map<String, String>> parse(String fileName, int sheetIndex) throws ExecutionException, InterruptedException { return sheetParser.parse(fileName, sheetIndex); } public static void main(String[] args) throws ExecutionException, InterruptedException { String fileName = "excel_file.xlsx"; ExecutorService executor = Executors.newFixedThreadPool(2); List<Future<List<Map<String, String>>>> futures = new ArrayList<>(); for (int i = 0; i < 2; i++) { SheetParser sheetParser = new EasyExcelSheetParser(); Future<List<Map<String, String>>> future = executor.submit(() -> sheetParser.parse(fileName, i)); futures.add(future); } List<Map<String, String>> data = new ArrayList<>(); for (Future<List<Map<String, String>>> future : futures) { data.addAll(future.get()); } System.out.println(data); executor.shutdown(); } } interface SheetParser { List<Map<String, String>> parse(String fileName, int sheetIndex); } class EasyExcelSheetParser implements SheetParser { @Override public List<Map<String, String>> parse(String fileName, int sheetIndex) { List<Map<String, String>> data = new ArrayList<>(); DataListener listener = new DataListener(data); ExcelReaderBuilder readerBuilder = EasyExcel.read(fileName, listener); Sheet sheet = new Sheet(sheetIndex + 1, 0); readerBuilder.sheet(sheet).doRead(); return data; } } class PoiSheetParser implements SheetParser { @Override public List<Map<String, String>> parse(String fileName, int sheetIndex) throws Exception { // 实现 POI 的解析,类似于前面的示例 } } class DataListener extends ReadListener<Data> { private List<Map<String, String>> data; private List<String> headers; public DataListener(List<Map<String, String>> data) { this.data = data; this.headers = new ArrayList<>(); } @Override public void onHeadRow(Row row) { for (Cell cell : row) { headers.add(cell.getStringCellValue()); } } @Override public void onRecord(Data data) { Map<String, String> record = new HashMap<>(); for (int i = 0; i < headers.size(); i++) { record.put(headers.get(i), data.getField(i)); } this.data.add(record); } } class Data { @ExcelProperty(index = 0) private String name; @ExcelProperty(index = 1) private int age; // Getters and setters } import java.util.List; import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class ExcelParser { private SheetParser sheetParser; @Autowired public ExcelParser(SheetParser sheetParser) { this.sheetParser = sheetParser; } public List<Map<String, String>> parse(String fileName, int sheetIndex) throws Exception { return sheetParser.parse(fileName, sheetIndex); } } interface SheetParser { List<Map<String, String>> parse(String fileName, int sheetIndex) throws Exception; } @Component class EasyExcelSheetParser implements SheetParser { @Override public List<Map<String, String>> parse(String fileName, int sheetIndex) throws Exception { List<Map<String, String>> data = new ArrayList<>(); DataListener listener = new DataListener(data); ExcelReaderBuilder readerBuilder = EasyExcel.read(fileName, listener); Sheet sheet = new Sheet(sheetIndex + 1, 0); readerBuilder.sheet(sheet).doRead(); return data; } } @Component class PoiSheetParser implements SheetParser { @Override public List<Map<String, String>> parse(String fileName, int sheetIndex) throws Exception { // 实现 POI 的解析,类似于前面的示例 } } class DataListener extends ReadListener<Data> { private List<Map<String, String>> data; private List<String> headers; public DataListener(List<Map<String, String>> data) { this.data = data; this.headers = new ArrayList<>(); } @Override public void onHeadRow(Row row) { for (Cell cell : row) { headers.add(cell.getStringCellValue()); } } @Override public void onRecord(Data data) { Map<String, String> record = new HashMap<>(); for (int i = 0; i < headers.size(); i++) { record.put(headers.get(i), data.getField(i)); } this.data.add(record); } } class Data { @ExcelProperty(index = 0) private String name; @ExcelProperty(index = 1) private int age; // Getters and setters } 原理学习: 知其然,并未知其所以然!直到看到下面的解惑视频…… 视频号搜索: 于建国博士讲解ChatGPT的工作原理及颠覆性影响,通俗易懂 (愿学有所得) 职责有边界、思考无边界、担当无边界!!! 利用好ChatGPT一切皆有可能,用好新时代的生产力工具希望ChatGPT能助力大家更上一层楼!

资源下载

更多资源
优质分享App

优质分享App

近一个月的开发和优化,本站点的第一个app全新上线。该app采用极致压缩,本体才4.36MB。系统里面做了大量数据访问、缓存优化。方便用户在手机上查看文章。后续会推出HarmonyOS的适配版本。

腾讯云软件源

腾讯云软件源

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

Nacos

Nacos

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

Spring

Spring

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

用户登录
用户注册