1. 云硬盘
- HDD(普通云盘)
特征: 性能一般, IOPS大概在数百左右。
应用场景: 数据不被经常访问或者低I/O负载的应用场景,需要低成本并且有随机读写I/O的应用环境。
- 混合HDD(高效云盘)
特征: 结合HDD和SSD硬盘构建, IOPS为1000~5000左右。
应用场景: 开发与测试业务、系统盘。
- SSD云盘
特征: 具有稳定的IO能力, IOPS能够达到10000~25000左右。
应用场景:I/O密集型应用、中小型关系数据库、NoSQL数据库。
- 企业级SSD(ESSD云盘)
特征: 优化增强的SSD云盘, 一般是采用企业级的闪存硬件, IOPS能够达到10000~1000000左右。
应用场景: 大型OLTP数据库等关系型数据库、NoSQL数据库、ELK分布式日志存储等。
测试:
- 安装fio工具
yum -y install fio
- iops测试
fio --name=disktest --filename=~/disktest --rw=randread --refill_buffers --bs=4k --size=1G -runtime=5 -direct=1 -iodepth=128 -ioengine=libaio
输出结果:
[root@iZm5egp1t778ocdk7f1j6fZ ~]# fio --name=disktest --filename=~/disktest --rw=randread --refill_buffers --bs=4k --size=1G -runtime=5 -direct=1 -iodepth=128 -ioengine=libaio disktest: (g=0): rw=randread, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=libaio, iodepth=128 fio-3.7 Starting 1 process Jobs: 1 (f=1): [r(1)][100.0%][r=8560KiB/s,w=0KiB/s][r=2140,w=0 IOPS][eta 00m:00s] disktest: (groupid=0, jobs=1): err= 0: pid=1417: Thu Nov 12 22:09:25 2020 read: IOPS=2151, BW=8606KiB/s (8812kB/s)(42.3MiB/5032msec) slat (usec): min=2, max=166, avg= 6.54, stdev= 3.69 clat (usec): min=794, max=157946, avg=59467.61, stdev=47971.33 lat (usec): min=799, max=157950, avg=59474.73, stdev=47971.03 clat percentiles (usec): | 1.00th=[ 1172], 5.00th=[ 1434], 10.00th=[ 1614], 20.00th=[ 1860], | 30.00th=[ 2147], 40.00th=[ 2999], 50.00th=[ 98042], 60.00th=[ 98042], | 70.00th=[ 99091], 80.00th=[ 99091], 90.00th=[ 99091], 95.00th=[100140], | 99.00th=[127402], 99.50th=[127402], 99.90th=[156238], 99.95th=[156238], | 99.99th=[158335] bw ( KiB/s): min= 8544, max= 8560, per=99.46%, avg=8558.40, stdev= 5.06, samples=10 iops : min= 2136, max= 2140, avg=2139.60, stdev= 1.26, samples=10 lat (usec): 1000=0.16% lat (msec): 2=25.15%, 4=15.13%, 10=0.03%, 50=0.59%, 100=56.23% lat (msec): 250=2.71% cpu : usr=0.58%, sys=1.93%, ctx=1154, majf=0, minf=163 IO depths : 1=0.1%, 2=0.1%, 4=0.1%, 8=0.1%, 16=0.1%, 32=0.3%, >=64=99.4% submit : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0% complete : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.1% issued rwts: total=10826,0,0,0 short=0,0,0,0 dropped=0,0,0,0 latency: target=0, window=0, percentile=100.00%, depth=128 Run status group 0 (all jobs): READ: bw=8606KiB/s (8812kB/s), 8606KiB/s-8606KiB/s (8812kB/s-8812kB/s), io=42.3MiB (44.3MB), run=5032-5032msec Disk stats (read/write): vda: ios=10486/0, merge=0/1, ticks=614779/0, in_queue=485314, util=75.89%
iops平均达到2139.60,与高效云盘标示的2120基本是一致。
也可以挂在动态硬盘进行测试, 不同的类型和存储空间, IOPS是不一样:
2. 对象存储
- Amazon S3 vs 阿里云 OSS
Amazon S3,全称亚马逊简易存储服务(Amazon Simple Storage Service)
阿里云 OSS(Object Storage Service,简称OSS),是阿里云对外提供的海量、安全、低成本、高可靠的云存储服务。
对比:
- 对象存储VS云硬盘
- 提供接口访问
对象存储本质是一个网络化的服务, 云硬盘是挂载到虚拟机的虚拟硬盘,必须连接到虚拟机才能操作。 - 存储结构不一致
- 云硬盘是一个可以作为一个真正的文件系统, 而云存储是一个近似键值(key和value)的存储服务。
- 海量数据存储
- 云硬盘一般会受自身容量的限制, 不能支撑海量数据存储, 对象存储得益于其底层设计, 天生就能够支撑大数据存储。对象存储服务不仅可以支持海量的小文件, 也适合处理大型文件。
- 实践操作
流程:
- 开通OSS服务OSS产品详情页
- 创建存储空间, Bucket名称要具备唯一性。
- 开通对应的访问权限
不要采用主账号,会存在安全隐患, 授权给RAM用户。
- 添加依赖
<dependency> <groupId>com.aliyun.oss</groupId> <artifactId>aliyun-sdk-oss</artifactId> <version>3.10.2</version> </dependency>
- 上传文件
UploadApplication:
public class UploadApplication { public static void main(String[] args) throws Exception{ // 创建OSSClient实例。 OSS ossClient = new OSSClientBuilder().build(Constants.endpoint, Constants.accessKeyId, Constants.accessKeySecret); // 创建PutObjectRequest对象。 PutObjectRequest putObjectRequest = new PutObjectRequest(Constants.bucketName, "readme", new File("d:/readme.txt")); // 上传文件。 PutObjectResult result = ossClient.putObject(putObjectRequest); System.out.println("upload complete."); // 关闭OSSClient。 ossClient.shutdown(); } }
- 下载文件
DownloadApplication:
public class DownloadApplication { public static void main(String[] args) { // Endpoint以杭州为例,其它Region请按实际情况填写。 String endpoint = Constants.endpoint; // 阿里云主账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM账号进行API访问或日常运维,请登录 https://ram.console.aliyun.com 创建RAM账号。 String accessKeyId = Constants.accessKeyId; String accessKeySecret = Constants.accessKeySecret; String bucketName = Constants.bucketName; String objectName = "readme"; // 创建OSSClient实例。 OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); // 下载OSS文件到本地文件。如果指定的本地文件存在会覆盖,不存在则新建。 ossClient.getObject(new GetObjectRequest(bucketName, objectName), new File("e:/"+ objectName)); // 关闭OSSClient。 ossClient.shutdown(); System.out.println("download complete."); } }
3. 表单上传案例
- 应用场景
表单上传非常适合嵌入在HTML网页中来上传Object,比较常见的场景是网站应用,以招聘网站为例, 流程比对:
- 不使用表单上传
- 网站用户上传简历。
- 网站服务器回应上传页面。
- 简历被上传到网站服务器。
- 网站服务器再将简历上传到OSS。
- 采用表单上传
- 网站用户上传简历。
- 网站服务器回应上传页面。
- 简历上传到OSS。
使用表单上传,少了一步转发流程, 并且在上传量过大时, 减少了业务应用方服务扩容的压力。
- 通过STS临时授权访问OSS
通过阿里云STS(Security Token Service)进行临时授权访问, 可以为第三方应用颁发一个自定义时效和权限的访问凭证, 用以保障服务安全性(类似于OAuth2的授权码访问模式)。
实现机制:
- RAM用户STS授权配置
进入RAM访问控制后台->用户->添加权限, 输入框填写"sts"过滤, 选择AliyunSTSAssumeRoleAccess权限。
- 创建权限策略
输入权限策略名称, 可以选择脚本配置, 更为灵活。
策略内容:
{ "Version": "1", "Statement": [ { "Effect": "Allow", "Action": [ "oss:*" ], "Resource": [ "acs:oss:*:*:cloudstorage-test", "acs:oss:*:*:cloudstorage-test/*" ] } ] }
意思是对名称为cloudstorage-test的Bucket具有完全控制权限。如果更细力度的控制, 可以修改Action,例如:
"Action": [ "oss:ListBuckets", "oss:GetBucketStat", "oss:GetBucketInfo", "oss:GetBucketTagging", "oss:GetBucketAcl" ],
- 创建访问角色
打开"RAM角色管理",点击"创建RAM角色“,可信实体类型选择“阿里云账号”
接下来输入角色名称, 选择当前云账号;添加上面所创建的权限策略“oss_access”。
配置完成后, 会生成一个ARN值, 需要将它记录下来。
- 申请STS的访问TOKEN信息
StsServiceApplication代码:
public class StsServiceApplication { public static void main(String[] args) { String endpoint = "sts.cn-beijing.aliyuncs.com"; String AccessKeyId = Constants.accessKeyId; String accessKeySecret = Constants.accessKeySecret; String roleArn = "acs:ram::1567235516853620:role/ram-oss-access"; String roleSessionName = "oss_access_session"; String policy = "{\n" + " \"Version\": \"1\", \n" + " \"Statement\": [\n" + " {\n" + "\"Action\": [\n" + " \"oss:*\"\n" + "], \n" + "\"Resource\": [\n" + " \"acs:oss:*:*:*\" \n" + "], \n" + "\"Effect\": \"Allow\"\n" + " }\n" + " ]\n" + "}"; try { // 添加endpoint(直接使用STS endpoint,前两个参数留空,无需添加region ID) DefaultProfile.addEndpoint("", "", "Sts", endpoint); // 构造default profile(参数留空,无需添加region ID) IClientProfile profile = DefaultProfile.getProfile("", AccessKeyId, accessKeySecret); // 用profile构造client DefaultAcsClient client = new DefaultAcsClient(profile); final AssumeRoleRequest request = new AssumeRoleRequest(); request.setMethod(MethodType.POST); request.setRoleArn(roleArn); request.setRoleSessionName(roleSessionName); request.setPolicy(policy); // 若policy为空,则用户将获得该角色下所有权限 request.setDurationSeconds(1000L); // 设置凭证有效时间 final AssumeRoleResponse response = client.getAcsResponse(request); System.out.println("Expiration: " + response.getCredentials().getExpiration()); System.out.println("Access Key Id: " + response.getCredentials().getAccessKeyId()); System.out.println("Access Key Secret: " + response.getCredentials().getAccessKeySecret()); System.out.println("Security Token: " + response.getCredentials().getSecurityToken()); System.out.println("RequestId: " + response.getRequestId()); } catch (ClientException e) { System.out.println("Failed:"); System.out.println("Error code: " + e.getErrCode()); System.out.println("Error message: " + e.getErrMsg()); System.out.println("RequestId: " + e.getRequestId()); } } }
返回结果:
Expiration: 2020-11-15T06:37:51Z Access Key Id: STS.NT2Mshx5eaKbLScAzcwXLLK5V Access Key Secret: 7buxRohgRr6vT1EVAqq4FWjxaUFRQMuC4vvV55utenkJ Security Token: CAISjwJ1q6Ft5B2yfSjIr5eHBsnclepE1omJTnXSpXo2e9dgo46etDz2IHxMenFgA+sfv/0ynGBR5/YSlrt0UIRyTEfPYNBr2Y9a6higZIyZdz4iUQhC2vOfAmG2J0PR7q27OpfELr70fvOqdCqz9Etayqf7cjOPRkGsNYbz57dsctUQWHvXD1dBH8wEZHEhyqkgOGDWKOymPzPzn2PUFzAIgAdnjn5l4qnNpa/54xHF3lrh0b1X9cajYLrcNpQyY80kDorsgrwrLfSbiBQ9sUYaqP1E64Vf4irCs92nBF1c3g6LKeK88Kc0cFcnPvhgQPcV9aWkxaQp6rzJ8Z7+zlNKJvoQWi/USZu70Fd2+ykG8lpTGoABiIGFt+WCBkX/yLkY3uHDiWq4Uud32DzXWQAQpGmOWXwYzPRepi0XCcC029hPoXwCsj6mWbd/Ls2bUQsLUPtG3ozr6WawG2XUBXgZI5dNip8dZJCWZSet9qGsNXubhA3hTC+Wi7MNOariEkmr1kjqnG6N/YNaWuMYJ3BUobvLL4g= RequestId: 480E0B98-ACA5-4C98-AA82-6D9901CD7EE4
- 表单上传
FormPostApplication代码:
public class FormPostApplication { // The local file path to upload. private String localFilePath = "d:/trade_stock.sql"; // OSS domain, such as http://oss-cn-hangzhou.aliyuncs.com private String endpoint = Constants.endpoint; // Access key Id. Please get it from https://ak-console.aliyun.com private String accessKeyId = "STS.NTcqigyooFzFUeV2GRZPWDLt8"; private String accessKeySecret = "HwdZYJ8wVUopdNscwDYFf7oPgBpA4WXgG6K4JggztqW9"; private String oss_security_token= "CAISjwJ1q6Ft5B2yfSjIr5fWOtPTlLBO8bitV0Pn1kcHVt97q4nf2jz2IHxMenFgA+sfv/0ynGBR5/YSlrt0UIRyTEfPYNBr2Y9a6higZIyZW2tYUAhC2vOfAmG2J0PR7q27OpfELr70fvOqdCqz9Etayqf7cjOPRkGsNYbz57dsctUQWHvXD1dBH8wEZHEhyqkgOGDWKOymPzPzn2PUFzAIgAdnjn5l4qnNpa/54xHF3lrh0b1X9cajYLrcNpQyY80kDorsgrwrLfSbiBQ9sUYaqP1E64Vf4irCs92nBF1c3g6LKeK88Kc0cFcnPvhgQPcV9aWkxaQp6rzJ8Z7+zlNKJvoQWi/USZu70Fd2+ykG8lpTGoABUJly6VzNT0fNrAW2uTyuuGX+PI/PaV7df5cewy7WoAnvtjDe0PM8vBWuD6zY3dQFgQOPFkY8RxxQUrSkZ9wpS3E7FBjzTfkFGlRHmmc+ad8uCLPaWIb/B9QGI6uKidSXnEnLqcK+8xsB1HBsyCkL600PJfcuSWBs9CHh9uiJTG0="; // The existing bucket name private String bucketName = Constants.bucketName; // The key name for the file to upload. private String key = "trade_stock"; private void postObject() throws Exception { // append the 'bucketname.' prior to the domain, such as http://bucket1.oss-cn-hangzhou.aliyuncs.com. String urlStr = endpoint.replace("http://", "http://" + bucketName + "."); // form fields Map<String, String> formFields = new LinkedHashMap<String, String>(); // key formFields.put("key", this.key); // Content-Disposition formFields.put("Content-Disposition", "attachment;filename=" + localFilePath); // OSSAccessKeyId formFields.put("OSSAccessKeyId", accessKeyId); // policy String policy = "{\"expiration\": \"2120-01-01T12:00:00.000Z\",\"conditions\": [[\"content-length-range\", 0, 104857600]]}"; String encodePolicy = new String(Base64.encodeBase64(policy.getBytes())); formFields.put("policy", encodePolicy); // Signature String signaturecom = computeSignature(accessKeySecret, encodePolicy); formFields.put("Signature", signaturecom); // Set security token. formFields.put("x-oss-security-token", oss_security_token); String ret = formUpload(urlStr, formFields, localFilePath); System.out.println("Post Object [" + this.key + "] to bucket [" + bucketName + "]"); System.out.println("post reponse:" + ret); } private static String computeSignature(String accessKeySecret, String encodePolicy) throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException { // convert to UTF-8 byte[] key = accessKeySecret.getBytes("UTF-8"); byte[] data = encodePolicy.getBytes("UTF-8"); // hmac-sha1 Mac mac = Mac.getInstance("HmacSHA1"); mac.init(new SecretKeySpec(key, "HmacSHA1")); byte[] sha = mac.doFinal(data); // base64 return new String(Base64.encodeBase64(sha)); } private static String formUpload(String urlStr, Map<String, String> formFields, String localFile) throws Exception { String res = ""; HttpURLConnection conn = null; // String boundary = "9431149156168"; String boundary = "abc"; try { URL url = new URL(urlStr); conn = (HttpURLConnection)url.openConnection(); conn.setConnectTimeout(5000); conn.setReadTimeout(30000); conn.setDoOutput(true); conn.setDoInput(true); conn.setRequestMethod("POST"); conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.6)"); // Set Content-MD5. The MD5 value is calculated based on the whole message body. //conn.setRequestProperty("Content-MD5", "<yourContentMD5>"); conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary); OutputStream out = new DataOutputStream(conn.getOutputStream()); // text if (formFields != null) { StringBuffer strBuf = new StringBuffer(); Iterator<Entry<String, String>> iter = formFields.entrySet().iterator(); int i = 0; while (iter.hasNext()) { Entry<String, String> entry = iter.next(); String inputName = entry.getKey(); String inputValue = entry.getValue(); if (inputValue == null) { continue; } if (i == 0) { strBuf.append("--").append(boundary).append("\r\n"); strBuf.append("Content-Disposition: form-data; name=\"" + inputName + "\"\r\n\r\n"); strBuf.append(inputValue); } else { strBuf.append("\r\n").append("--").append(boundary).append("\r\n"); strBuf.append("Content-Disposition: form-data; name=\"" + inputName + "\"\r\n\r\n"); strBuf.append(inputValue); } i++; } out.write(strBuf.toString().getBytes()); } // file File file = new File(localFile); String filename = file.getName(); String contentType = new MimetypesFileTypeMap().getContentType(file); if (contentType == null || contentType.equals("")) { contentType = "application/octet-stream"; } StringBuffer strBuf = new StringBuffer(); strBuf.append("\r\n").append("--").append(boundary) .append("\r\n"); strBuf.append("Content-Disposition: form-data; name=\"file\"; " + "filename=\"" + filename + "\"\r\n"); strBuf.append("Content-Type: " + contentType + "\r\n\r\n"); out.write(strBuf.toString().getBytes()); DataInputStream in = new DataInputStream(new FileInputStream(file)); int bytes = 0; byte[] bufferOut = new byte[1024]; while ((bytes = in.read(bufferOut)) != -1) { out.write(bufferOut, 0, bytes); } in.close(); byte[] endData = ("\r\n--" + boundary + "--\r\n").getBytes(); out.write(endData); out.flush(); out.close(); // Gets the file data strBuf = new StringBuffer(); BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream())); String line = null; while ((line = reader.readLine()) != null) { strBuf.append(line).append("\n"); } res = strBuf.toString(); reader.close(); reader = null; } catch (Exception e) { System.err.println("Send post request exception: " + e); throw e; } finally { if (conn != null) { conn.disconnect(); conn = null; } } return res; } public static void main(String[] args) throws Exception { FormPostApplication ossPostObject = new FormPostApplication(); ossPostObject.postObject(); } }
- 将上面所获取的key,secret和token填入。
- bucket名称要和上面授权对应的bucket一致。
- 这里是模拟form表单提交,编码采用UTF-8。
- policy里面可以配置超时时间, 内容长度范围等。
- 如果出现403错误,检查token等权限信息的配置是否正确。
- 如果出现400错误, 检查参数配置是否正确, 比如说MD5参数如果传递, 但没配置正确, 会出现此错误。
conn.setRequestProperty("Content-MD5", "<yourContentMD5>");
操作成功后, 能够在后台看到对应的文件信息。