IaaS(Infrastructure as a Service )提供托管的 IT 基础架构,供用户调配处理能力、存储、网络和其他基础计算资源。IaaS 提供商运行并管理此基础架构,用户可以在此基础架构上运行选择的操作系统和应用程序软件。
在云平台中还会涉及以下概念:
如何区分 PaaS、IaaS 、SaaS?
按照业务功能来划分应用服务,整个职责更清晰,相互之间可以做到独立升级迭代。应用之间可能会涉及到一些公共配置,可以通过分布式配置中心Zookeeper来解决。
架构瓶颈:
不同应用服务之间存在共用的组件,会导致相同代码存在多份,公共功能升级时全部应用代码都要跟着升级。
比如说JSON字符串处理组件, 加密处理组件等
如用户管理、订单、支付、鉴权等功能在多个应用中都存在,那么可以把这些功能的代码单独抽取出来形成一个单独的服务来管理, 比如说加密封装,鉴权处理接口等。
微服务架构,应用和服务之间通过HTTP、TCP或RPC请求等多种方式来访问公共服务,每个单独的服务都可以由单独的团队来管理。
在服务治理层面, 可以通过SpringCloud等框架实现服务治理、限流、熔断、降级等功能,提高服务的稳定性和可用性。
架构瓶颈:
不同服务的接口访问方式不同,应用服务可能需要适配多种访问方式, 才能使用服务,应用服务之间也可能相互访问,调用链将会变得非常复杂冗长,逻辑变得混乱。
应用统一通过ESB来访问后端服务,服务与服务之间也通过ESB来相互调用,以此降低系统的耦合程度。
使用企业消息总线来解除服务之间耦合问题的架构,就是所谓的SOA(面向服务)架构,这种架构与微服务架构容易混淆,因为表现形式比较相似。
微服务架构更多是指把系统里的公共服务抽取出来单独运维管理的思想,而SOA架构则是指一种拆分服务并使服务接口访问变得统一的架构思想,SOA架构中包含了微服务的思想。
架构瓶颈:
业务不断发展,应用和服务都会不断变多,应用和服务的部署变得复杂,同一台服务器上部署多个服务还要解决运行环境冲突的问题。
对于如大促这类需要动态扩缩容的场景,需要水平扩展服务的性能,就需要在新增的服务上准备运行环境,部署服务等,运维将变得十分困难。
目前最流行的容器化技术是Docker,最流行的容器管理服务是Kubernetes(K8S),应用/服务可以打包为Docker镜像,通过K8S来动态分发和部署镜像。
Docker镜像可理解为一个能运行你的应用/服务的最小的操作系统,里面放着应用/服务的运行代码,运行环境根据实际的需要设置好。
把整个“操作系统”打包为一个镜像后,就可以分发到需要部署相关服务的机器上,直接启动Docker镜像就可以把服务启起来,使服务的部署和运维变得简单。
架构瓶颈:
使用容器化技术后服务动态扩缩容问题得以解决,但是机器还是需要公司自身来管理,在非大促的时候,还是需要闲置着大量的机器资源来应对大促,机器自身成本和运维成本都极高,资源利用率低。
系统可部署到公有云上,利用公有云的海量机器资源,解决动态硬件资源的问题
在大促的时间段里,在云平台中临时申请更多的资源,结合Docker和K8S来快速部署服务,在大促结束后释放资源,真正做到按需付费,资源利用率大大提高,同时大大降低了运维成本。
所谓的云平台,就是把海量机器资源,通过统一的资源管理,抽象为一个资源整体
在云平台上可按需动态申请硬件资源(如CPU、内存、网络等),并且之上提供通用的操作系统,
提供常用的技术组件(如Hadoop技术栈,MPP数据库等)供用户使用,甚至提供开发好的应用用户不需要关心应用内部使用了什么技术,就能够解决需求(如音视频转码服务、邮件服务、个人博客等)。
yum -y install java-1.8.0-openjdk.x86_64
打包服务
maven clean install
随便新建一个springboot项目,访问验证下
yum -y install fio
fio --name=disktest --filename=~/disktest --rw=randread --refill_buffers --bs=4k --size=1G -runtime=5 -direct=1 -iodepth=128 -ioengine=libaio
[root@izvy205yn6w6mn9hrwfd54z ~]# 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
disktest: Laying out IO file (1 file / 1024MiB)
Jobs: 1 (f=1): [r(1)][100.0%][r=9600KiB/s,w=0KiB/s][r=2400,w=0 IOPS][eta 00m:00s]
disktest: (groupid=0, jobs=1): err= 0: pid=13013: Mon Nov 1 10:47:11 2021
read: IOPS=2431, BW=9727KiB/s (9960kB/s)(47.6MiB/5011msec)
slat (nsec): min=1727, max=1183.5k, avg=12073.65, stdev=23694.23
clat (usec): min=1299, max=125857, avg=52597.48, stdev=46119.34
lat (usec): min=1307, max=125874, avg=52610.26, stdev=46119.12
clat percentiles (usec):
| 1.00th=[ 1975], 5.00th=[ 2409], 10.00th=[ 2737], 20.00th=[ 3326],
| 30.00th=[ 4015], 40.00th=[ 5473], 50.00th=[ 90702], 60.00th=[ 94897],
| 70.00th=[ 95945], 80.00th=[ 96994], 90.00th=[ 99091], 95.00th=[ 99091],
| 99.00th=[103285], 99.50th=[106431], 99.90th=[115868], 99.95th=[117965],
| 99.99th=[125305]
bw ( KiB/s): min= 8864, max=10256, per=99.16%, avg=9644.40, stdev=455.07, samples=10
iops : min= 2216, max= 2564, avg=2411.10, stdev=113.77, samples=10
lat (msec) : 2=1.23%, 4=28.49%, 10=16.21%, 20=1.38%, 50=0.17%
lat (msec) : 100=48.80%, 250=3.73%
cpu : usr=0.80%, sys=4.11%, ctx=1066, majf=0, minf=160
IO depths : 1=0.1%, 2=0.1%, 4=0.1%, 8=0.1%, 16=0.1%, 32=0.3%, >=64=99.5%
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=12185,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=9727KiB/s (9960kB/s), 9727KiB/s-9727KiB/s (9960kB/s-9960kB/s), io=47.6MiB (49.9MB), run=5011-5011msec
iops平均达到2411,与高效云盘标示基本是一致。
也可以挂载动态硬盘进行测试, 不同的类型和存储空间, IOPS是不一样:
Amazon S3 vs 阿里云 OSS
Amazon S3,全称亚马逊简易存储服务(Amazon Simple Storage Service)
阿里云 OSS(Object Storage Service,简称OSS),是阿里云对外提供的海量、安全、低成本、高可靠的云存储服务。
对象存储VS云硬盘
提供接口访问
对象存储本质是一个网络化的服务, 云硬盘是挂载到虚拟机的虚拟硬盘,必须连接到虚拟机才能操作。
存储结构不一致
云硬盘是一个可以作为一个真正的文件系统, 而云存储是一个近似键值(key和value)的存储服务。
海量数据存储
云硬盘一般会受自身容量的限制, 不能支撑海量数据存储, 对象存储得益于其底层设计, 天生就能够支撑大数据存储。对象存储服务不仅可以支持海量的小文件, 也适合处理大型文件。
开通OSS服务OSS产品详情页
创建存储空间, Bucket名称要具备唯一性。
添加依赖
<dependency>
<groupId>com.aliyun.ossgroupId>
<artifactId>aliyun-sdk-ossartifactId>
<version>3.10.2version>
dependency>
public static void main(String[] args) throws Exception{
// 1. 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(Constants.endpoint, Constants.accessKeyId, Constants.accessKeySecret);
// 2.创建PutObjectRequest对象。
PutObjectRequest putObjectRequest = new PutObjectRequest(Constants.bucketName, "readme", new File("d:/readme.txt"));
// 3.上传文件。
PutObjectResult result = ossClient.putObject(putObjectRequest);
System.out.println("upload complete.");
// 4.关闭OSSClient。
ossClient.shutdown();
}
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.");
}
{
"Version": "1",
"Statement": [
{
"Effect": "Allow",
"Action": [
"oss:*"
],
"Resource": [
"acs:oss:*:*:cloudstorage-test01",
"acs:oss:*:*:cloudstorage-test01/*"
]
}
]
}
意思是对名称为cloudstorage-test01的Bucket具有完全控制权限。如果更细力度的控制, 可以修改Action,例如
"Action": [
"oss:ListBuckets",
"oss:GetBucketStat",
"oss:GetBucketInfo",
"oss:GetBucketTagging",
"oss:GetBucketAcl"
],
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/0ynGBR
5/YSlrt0UIRyTEfPYNBr2Y9a6higZIyZdz4iUQhC2vOfAmG2J0PR7q27OpfELr70fvOqdCqz9Eta
yqf7cjOPRkGsNYbz57dsctUQWHvXD1dBH8wEZHEhyqkgOGDWKOymPzPzn2PUFzAIgAdnjn5l4qnN
pa/54xHF3lrh0b1X9cajYLrcNpQyY80kDorsgrwrLfSbiBQ9sUYaqP1E64Vf4irCs92nBF1c3g6L
KeK88Kc0cFcnPvhgQPcV9aWkxaQp6rzJ8Z7+zlNKJvoQWi/USZu70Fd2+ykG8lpTGoABiIGFt+WC
BkX/yLkY3uHDiWq4Uud32DzXWQAQpGmOWXwYzPRepi0XCcC029hPoXwCsj6mWbd/Ls2bUQsLUPtG
3ozr6WawG2XUBXgZI5dNip8dZJCWZSet9qGsNXubhA3hTC+Wi7MNOariEkmr1kjqnG6N/YNaWuMY
J3BUobvLL4g=
RequestId: 480E0B98-ACA5-4C98-AA82-6D9901CD7EE4
public class Constants {
// Endpoint以杭州为例,其它Region请按实际情况填写。
public static final String endpoint = "http://oss-cn-beijing.aliyuncs.com";
// 阿里云主账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM账号进行API访问或日常运维,请登录 https://ram.console.aliyun.com 创建RAM账号。
public static final String accessKeyId = "LTAIpacnCebUBRBw";
public static final String accessKeySecret = "LLzgImuYylRvUe4p0KjQ6eemdeXA1A";
// 存储bucket名称
public static final String bucketName = "cloudstorage-test";
}
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.NSvgcW651Ho6M4uQ727GyFQtL";
private String accessKeySecret = "AdQDN9pXmzxQp1fszxFfQyCCvaviP6v9zM2cTpgfRvUY";
private String oss_security_token= "CAISjwJ1q6Ft5B2yfSjIr5DDLNnj2+oQ/63dTxLEtTdnO8hVqZTfrjz2IHxMenFgA+sfv/0ynGBR5/YSlrt0UIRyTEfPYNBr2Y9a6higZIyZcXZwbzZC2vOfAmG2J0PR7q27OpfELr70fvOqdCqz9Etayqf7cjOPRkGsNYbz57dsctUQWHvXD1dBH8wEZHEhyqkgOGDWKOymPzPzn2PUFzAIgAdnjn5l4qnNpa/54xHF3lrh0b1X9cajYLrcNpQyY80kDorsgrwrLfSbiBQ9sUYaqP1E64Vf4irCs92nBF1c3g6LKeK88Kc0cFcnPvhgQPcV9aWkxaQp6rzJ8Z7+zlNKJvoQWi/USZu70Fd2+ykG8lpTGoABraRqM0OmXEoRftERlYR30DNCLEbM9pxhm7QDPpYAtO/c2iq7wpuJ1MLcPJGck7LJw+qqpHfsi6f/wYM+7Y4mi62fxAbWxRrftAmkxIV4oWTRhC13G4dnv1HMEvJAhEkhhh1PLP1+XeuARFMRyy9IvIrC9TDjagb4dyds4uMQVa4=";
// 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", "");
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();
}
}
conn.setRequestProperty("Content-MD5", "" );
云数据库的高级特性:
云数据库都自带性能分析和改进的模块, 能够自动地发现性能热点,还能够智能地给出调整建议,比如进行个别语句的调整,添加额外的索引等等。云数据库的性能分析和自动调优的能力,是将生产运行数据和服务内置的 AI 模型进行了结合,做到了真正的智能化运维, 极大的节省了成本。
阿里云的数据库自治服务DAS:
自治服务DAS是一种基于机器学习和专家经验实现数据库自感知、自修复、自优化、自运维及自安全的云服务,使用了DAS之后您可以避免这样的复杂性和人工操作引起的故障,有效保障数据库服务的稳定、安全及高效。
# 数据源配置, 采用Druid
datasource:
tradesystem:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://rm-m5e3390y2m54wbmhz35890bm.mysql.rds.aliyuncs.com:3306/smooth?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=UTC
username: mirson
password: Hxx654321
druid:
# 连接池的配置信息
# 初始化大小,最小,最大
initial-size: 5
min-idle: 5
maxActive: 20
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
# 打开PSCache,并且指定每个连接上PSCache的大小
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
filters: stat,wall,log4j
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
#connectionProperties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000
更强的性能与扩展性
云原生数据库由于原生设计, 专门为云设计的专业化存储架构, 可以支撑更大规模的数据
量,关系型云原生数据库能够脱离典型的数 TB 的容量上限,达到单库数十 TB 甚至百 TB 的级别。
云原生数据库可以利用云快速地进行水平扩展,迅速调整、提升数据库的处理能力, 能够有效应对高并发场景。
更高的可用性与可靠性
云原生数据库默认就具备多副本高可用的,数据同步、读写分离等高级特性,比如Amazon
Aurora云原生数据库, 就自动包含了分布在 3 个可用区、多达 6 份的数据副本。
对于多种数据模型也有很好的支持, 除了兼容关系型数据库外, 还会推出适合不同形态和查询范式的云数据库,与 NoSQL 数据库形成竞争, 比如说AWS的图数据库 Neptune,Azure Cosmos DB的NoSQL 数据库服务。
低成本与易维护性
大部分云原生数据库, 在存储上不需要预先设置大小, 会随着存储占用自动扩展;在计算
上, 也有部分云数据库推出了无服务器版本,比如 亚马逊 的 Aurora Serverless,在面对间歇偶发性工作负载时,都能节省较多的成本。
阿里云 PolarDB 放弃了通用分布式数据库OLTP多路并发写的支持,采用一写多读的架构设计,存储与计算分离的技术架构,简化了分布式系统难以兼顾的理论模型,又能满足绝大多数OLTP的应用场景和性能要求。
PolarDB 的设计革新:
申请外网连接地址
如果是内部虚拟机, 通过vpc内部网络接入, 要选择内网地址。
CREATE TABLE user_log (
userId INT(11) NOT NULL,
name VARCHAR(64) NOT NULL,
operation VARCHAR(128) DEFAULT NULL,
actionDate DATE DEFAULT NULL
) DBPARTITION BY HASH(userId) TBPARTITION BY WEEK(actionDate) TBPARTITIONS 7
PolarDB-X将拆分键值通过拆分函数计算得到一个计算结果,然后根据这个结果将数据分拆到私有定制RDS实例上。
创建完成之后, 可以看到对应的信息:
5.如何配置分片数
在实际应用中, 经常会面临分库分表的场景, PolarDB-X建议单个物理分表的容量不超过500万行数据。通常可以预估1~2年内的数据增长量,用估算出的总数据量除以总的物理分库数,再除以建议的单个物理分表的最大数据量(即500万),即可得出每个物理分库上需要创建的物理分表数。
计算公式:
物理分库上的物理分表数=向上取整(估算的总数据量/(私有定制RDS实例数 x 8)/ 5,000,000)
示例:
假设预估一张表在2年后的总数据量约为1亿行,如果已购买了2个私有定制RDS实例,那么按照分片数公式进行如下计算:
物理分库上的物理分表数= CEILING(100,000,000 / ( 2 * 8 ) / 5,000,000) = CEILING(1.25) = 2
结果为2,那么需要在每个物理分库上再创建2张物理分表。
6. 连接池配置
在实际生产当中, 官方推荐使用Druid连接池(最低要求版本1.1.11)
Druid与Spring的集成配置示例:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://ip:port/db?autoReconnect=true&rewriteBatchedStatements=true&socketTimeout=30000&connectTimeout=3000" />
<property name="username" value="root" />
<property name="password" value="123456" />
<property name="maxActive" value="20" />
<property name="initialSize" value="3" />
<property name="minIdle" value="3" />
<property name="maxWait" value="60000" />
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<property name="minEvictableIdleTimeMillis" value="300000" />
<property name="validationQuery" value="select 'z' from dual" />
<property name="testWhileIdle" value="true" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="phyTimeoutMillis" value="1800000" />
<property name="phyMaxUseCount" value="10000" />
bean>
beans>
DNS负载均衡, 原理是给用户返回不同的IP地址, 例如:
解析返回得到的 IP 地址可以是轮询, 也可以是随机得到的 IP 地址
健康检查:
支持ping、telnet、http(s)协议实时健康检查,获取应用服务运行状态。
故障切换
支持根据健康检查结果自动或者手工进行failover切换操作,实现主备切换、自动修改故障域名的解析,对异常的地址(服务)进行故障隔离或切换。
智能DNS
支持根据不同运营商、区域进行智能DNS解析,实现用户就近访问。
更多区别,
主要步骤: 创建实例 -> 配置访问策略 -> 主域名设置CNAME解析到实例的CNAME接入域名。
两台虚拟机都部署相同的服务(app-server), 用于高可用的测试验证。
全局配置
这里可以采用系统分配生成的cname域名, 主域名是用户访问应用服务使用的域名,必须填写真实主域名, 这里主域名是配置: test.mirson.cn。
开启健康检查
需要对地址池里的IP地址配置健康检查,以获取应用服务的可用性,从而达到根据应用服务地址可用性的状态实现自动故障隔离以及故障自动切换。
将地址池改为218.253.0.76不可用地址或停止服务, 开启健康检查后,会自动出现报警提示,并切换为备用地址池。
全球加速可以为不同地域的客户端智能返回不同的加速IP,降低解析时延,如果是面向国际的服务,是需要开启此功能, 如果只是国内使用, 可以不用开启。
详细操作, 查阅官方文档。
不同运营商会有自身专有的网络, 如果跨运营商访问存在不稳定的情况, 可以开启此功能。
实现原理
详细操作, 查阅官方文档。
部署方案:
为了实现全球用户都能获得较好的访问质量,通常企业会在中国大陆和海外分别部署至少两套以上的接入服务点,后端数据服务仍然使用一套。通过DNS服务,对于不同地区的用户请求流量做智能调度,将用户访问请求流量路由至不同的接入服务点。出现故障灾难时,各接入站点自建互相备份,最终实现业务的高可用。
操作配置说明
企业应用服务一般会有多个IP,且多个IP地址可能分布于不同地区。可以采用流量平均分配原则,对多个IP地址进行负载均摊,实现用户访问同一个应用服务域名时多个IP地址同时承担用户的访问请求。
实现方案:
平均分配与加权分配配置
默认的缓存时间计算规则, 要符合3个条件:
缓存规则示例解析:
通过阿里云CDN实现ECS上静态资源加速, ECS上可存储的资源包括静态资源和动态资源。
访问ECS上的资源时,动态资源请求直接返回,静态资源通过CDN实现访问加速,由CDN节点返回
操作步骤:
背景
弹性伸缩是云服务架构的重要优势,能够很好的解决高并发场景下的性能瓶颈, 同时节省运营成本。
在 IaaS 端,能够弹性伸缩的最实用的产品形态,一般是虚拟机编组。阿里云提供了弹性伸缩的功能。
要实现弹性伸缩服务, 还需要负载均衡器作为辅助组件,它可以将流量均匀地,或者按照一定权重或规则,分发到多台虚拟机上。
创建负载均衡SLB
协议监听配置:
这里配置的80端口, 可以根据需要, 在高级配置里面, 设置不同的调度算法: 加权轮询 (WRR)、加权最小连接数 (WLC)、轮询 (RR)、一致性哈希 (CH)。
接下来, 设定ECS服务的运行端口:
最后确认提交即可。
配置服务运行方式(服务部署方式)
编写服务接口
计算斐波那契数列:
@Controller
public class ElasticController {
static int[] flag=new int[1000001];
public int fibonacci(int n){
if(n==1||n==2){
flag[n]=1;
return 1;
}
else{
if(flag[n-1]!=0&&flag[n-2]!=0){
flag[n]=(flag[n-1]+flag[n-2])%10007;
return flag[n];
}
else
{
flag[n]=(fibonacci(n-2) + fibonacci(n-1))%10007;
return flag[n];
}
}
}
/**
* 计算斐波那契数列
* @param response
* @return
*/
@GetMapping("/calcFib")
public String calcFib(Integer num, HttpServletResponse response) {
try {
InetAddress localHost = Inet4Address.getLocalHost();
String ip = localHost.getHostAddress();
response.getWriter().println("result: " + fibonacci(num));
response.getWriter().println("server ip: " + ip);
}catch(Exception e) {
e.printStackTrace();
}
return null;
}
}
打包上传服务
maven clean install
设置开机启动
创建开机脚本:
vi /usr/lib/systemd/system/elastic.service
添加以下内容
[Unit]
Description=elasticservice
After=network.target remote-fs.target nss-lookup.target
[Service]
Type=simple
PIDFile=/run/elasticservice.pid
ExecStart=/usr/local/elasticstart.sh
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s QUIT $MAINPID
ExecReload=
PrivateTmp=true
RemainAfterExit=yes
ExecStartPre=
[Install]
WantedBy=multi-user.target
创建elasticstart.sh脚本:
#!/bin/bash
nohup /usr/bin/java -jar /usr/local/app-basic.jar >/dev/null 2>&1 &
设置权限:
chmod 777 /usr/local/elasticstart.sh
设置开机启动:
systemctl enable elastic
启动服务:
systemctl restart elastic
wget https://nodejs.org/dist/v10.13.0/node-v10.13.0-linux-x64.tar.xz
解压:
tar -xvf node-v10.13.0-linux-x64.tar.xz
创建软链接:
ln -s /usr/local/node-v10.13.0-linux-x64/bin/node /usr/bin/node
ln -s /usr/local/node-v10.13.0-linux-x64/bin/npm /usr/bin/npm
安装运行模块依赖
npm install express
npm install ip
npm install os
创建server.js脚本:
vi /usr/local/server.js :
const express = require('express');
const ip = require('ip');
const os = require('os');
const app = express();
//使用递归方式计算斐波那契数列
function fibo (n) {
return n > 1 ? fibo(n-1) + fibo(n-2) : 1;
}
app.get('/', function(req,res) {res.write('I am healthy'); res.end();}
);
app.get('/fibo/:n', function(req, res) {
var n = parseInt(req.params['n']);
var f = fibo(n);
res.write(`Fibo(${n}) = ${f} \n`);
res.write(`Server: ${os.hostname()} , private ip: ${ip.address()}
\n`);
res.end();
});
app.listen(80);
运行程序
node server.js
设置开机启动
创建开机脚本:
vi /usr/lib/systemd/system/nodeapp.service
添加以下内容:
[Unit]
Description=nodeappservice
After=network.target remote-fs.target nss-lookup.target
[Service]
Type=simple
PIDFile=/run/nodeapp.pid
ExecStart=/bin/setsid /usr/bin/node /usr/local/server.js
Restart=/bin/pkill node && /bin/setsid /usr/bin/node
/usr/local/server.js
ExecStop=/bin/pkill node
ExecReload=
PrivateTmp=true
RemainAfterExit=yes
ExecStartPre=
[Install]
WantedBy=multi-user.target
设置开机启动:
systemctl enable nodeapp
[root@localhost siege-4.0.2]# curl http://47.104.145.210/fibo/35
Fibo(35) = 14930352
Server: iZm5egp1t778ocdk7f1j6fZ , private ip: 172.31.141.105
正常能够返回主机名称和IP信息。
8. 创建自定义镜像
用于弹性机器扩容使用
7. 创建伸缩组
进入【部署与弹性】-【弹性伸缩】,配置信息来源, 选择【自定义伸缩配置】, 这里需要【创建伸缩配置】,在里面配置上面所自定义生成的镜像, 否则服务不能正常运行, 整个伸缩功能也就无法实现。
设定实例范围: 2-6台。(期望实例数, 会随着伸缩自动增长, 但不会超过最大实例数。)
设定扩容策略为均衡分布策略,在实际应用中, 建议虚拟交换机可以划分在不同区域,以保障高可用
创建完成
8. 创建弹性伸缩规则
这里设定CPU使用率不能超过30%。
9. 负载测试验证
进入负载均衡SLB,可以看到已经自动配置了初始的两台实例, 权重都设定为50:
通过测试,可以看到负载均衡已经生效:
[root@localhost siege-4.0.2]# curl http://47.104.145.210/fibo/35
Fibo(35) = 14930352
Server: iZm5ecgyf8ael3v9zrtx89Z , private ip: 172.31.141.111
[root@localhost siege-4.0.2]# curl http://47.104.145.210/fibo/35
Fibo(35) = 14930352
Server: iZm5egp1t778ocdk7f1j6fZ , private ip: 172.31.141.105
wget http://download.joedog.org/siege/siege-4.0.2.tar.gz
解压:
tar -zvxf siege-4.0.2.tar.g
安装依赖:
yum -y install gcc
编译安装:
cd siege-4.0.2
./configure
make
make install
[root@localhost siege-4.0.2]# siege -c 255 -t 10m
http://47.104.145.210/calcFib?num=99
[alert] Zip encoding disabled; siege requires zlib support to enable it
** SIEGE 4.0.2
** Preparing 200 concurrent users for battle.
The server is now under siege...
HTTP/1.1 200 0.22 secs: 84 bytes ==> GET /fibo/99
HTTP/1.1 200 0.22 secs: 84 bytes ==> GET /fibo/99
HTTP/1.1 200 0.22 secs: 84 bytes ==> GET /fibo/99
HTTP/1.1 200 0.22 secs: 84 bytes ==> GET /fibo/99
HTTP/1.1 200 0.22 secs: 84 bytes ==> GET /fibo/99
-c 是并发量,-t 是压测时间。持续数6,7分钟后, 可以看到自动伸缩生效,创建了新的实例
这里自动伸缩是会监控一段时间再执行,所以在测试过程中需要等待一段时间, 具体规则可以查看:
[root@localhost ~]# curl https://1567235516853620.cn-
qingdao.fc.aliyuncs.com/2016-08-15/proxy/guide-hello_world/calc_test/
{
"path": "/",
"queries": {},
"headers": {
"accept": "*/*",
"host": "1567235516853620.cn-qingdao.fc.aliyuncs.com",
"user-agent": "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7
NSS/3.27.1 zlib/1.2.3 libidn/1.18 libssh2/1.4.2",
"x-forwarded-proto": "https"
},
"method": "GET",
"requestURI": "/2016-08-15/proxy/guide-hello_world/calc_test/",
"clientIP": "113.118.76.129",
"body": ""
}