首页 文章 精选 留言 我的

精选列表

搜索[水印魔术手],共7333篇文章
优秀的个人博客,低调大师

手撸大文件上传

各位看官大家好,今天给大家分享的又是一篇实战文章,希望大家能够喜欢。 开味菜 最近接到一个新的需求,需要上传2G左右的视频文件,用测试环境的OSS试了一下,上传需要十几分钟,再考虑到公司的资源问题,果断放弃该方案。 一提到大文件上传,我最先想到的就是各种网盘了,现在大家都喜欢将自己收藏的小电影上传到网盘进行保存。网盘一般都支持断点续传和文件秒传功能,减少了网络波动和网络带宽对文件的限制,大大提高了用户体验,让人爱不释手。 说到这,大家先来了解一下这几个概念: 文件分块:将大文件拆分成小文件,将小文件上传\下载,最后再将小文件组装成大文件; 断点续传:在文件分块的基础上,将每个小文件采用单独的线程进行上传\下载,如果碰到网络故障,可以从已经上传\下载的部分开始继续上传\下载未完成的部分,而没有必要从头开始上传\下载; 文件秒传:资源服务器中已经存在该文件,其他人上传时直接返回该文件的URI。 RandomAccessFile 平时我们都会使用FileInputStream,FileOutputStream,FileReader以及FileWriter等IO流来读取文件,今天我们来了解一下RandomAccessFile。 它是一个直接继承Object的独立的类,底层实现中它实现的是DataInput和DataOutput接口。该类支持随机读取文件,随机访问文件类似于文件系统中存储的大字节数组。 它的实现基于文件指针(一种游标或者指向隐含数组的索引),文件指针可以通过getFilePointer方法读取,也可以通过seek方法设置。 输入时从文件指针开始读取字节,并使文件指针超过读取的字节,如果写入超过隐含数组当前结尾的输出操作会导致扩展数组。该类有四种模式可供选择: r: 以只读方式打开文件,如果执行写入操作会抛出IOException; rw: 以读、写方式打开文件,如果文件不存在,则尝试创建文件; rws: 以读、写方式打开文件,要求对文件内容或元数据的每次更新都同步写入底层存储设备; rwd: 以读、写方式打开文件,要求对文件内容的每次更新都同步写入底层存储设备; 在rw模式下,默认是使用buffer的,只有cache满的或者使用RandomAccessFile.close()关闭流的时候才真正的写到文件。 API 1、void seek(long pos):设置下一次读取或写入时的文件指针偏移量,通俗点说就是指定下次读文件数据的位置。 偏移量可以设置在文件末尾之外,只有在偏移量设置超出文件末尾后,才能通过写入更改文件长度; 2、native long getFilePointer():返回当前文件的光标位置; 3、native long length():返回当前文件的长度; 4、读方法 5、写方法 6、readFully(byte[] b):这个方法的作用就是将文本中的内容填满这个缓冲区b。如果缓冲b不能被填满,那么读取流的过程将被阻塞,如果发现是流的结尾,那么会抛出异常; 7、FileChannel getChannel():返回与此文件关联的唯一FileChannel对象; 8、int skipBytes(int n):试图跳过n个字节的输入,丢弃跳过的字节; RandomAccessFile的绝大多数功能,已经被JDK1.4的NIO的内存映射文件取代了,即把文件映射到内存后再操作,省去了频繁磁盘io。 主菜 总结经验,砥砺前行:之前的实战文章中过多的粘贴了源码,影响了各位小伙伴的阅读感受。经过大佬的点拨,以后将展示部分关键代码,供各位赏析,源码可在后台获取。 文件分块 文件分块需要在前端进行处理,可以利用强大的js库或者现成的组件进行分块处理。需要确定分块的大小和分块的数量,然后为每一个分块指定一个索引值。 为了防止上传文件的分块与其它文件混淆,采用文件的md5值来进行区分,该值也可以用来校验服务器上是否存在该文件以及文件的上传状态。 如果文件存在,直接返回文件地址; 如果文件不存在,但是有上传状态,即部分分块上传成功,则返回未上传的分块索引数组; 如果文件不存在,且上传状态为空,则所有分块均需要上传。 fileRederInstance.readAsBinaryString(file); fileRederInstance.addEventListener("load", (e) => { let fileBolb = e.target.result; fileMD5 = md5(fileBolb); const formData = new FormData(); formData.append("md5", fileMD5); axios .post(http + "/fileUpload/checkFileMd5", formData) .then((res) => { if (res.data.message == "文件已存在") { //文件已存在不走后面分片了,直接返回文件地址到前台页面 success && success(res); } else { //文件不存在存在两种情况,一种是返回data:null代表未上传过 一种是data:[xx,xx] 还有哪几片未上传 if (!res.data.data) { //还有几片未上传情况,断点续传 chunkArr = res.data.data; } readChunkMD5(); } }) .catch((e) => {}); }); 在调用上传接口前,通过slice方法来取出索引在文件中对应位置的分块。 const getChunkInfo = (file, currentChunk, chunkSize) => { //获取对应下标下的文件片段 let start = currentChunk * chunkSize; let end = Math.min(file.size, start + chunkSize); //对文件分块 let chunk = file.slice(start, end); return { start, end, chunk }; }; 之后调用上传接口完成上传。 断点续传、文件秒传 后端基于spring boot开发,使用redis来存储上传文件的状态和上传文件的地址。 如果文件完整上传,返回文件路径;部分上传则返回未上传的分块数组;如果未上传过返回提示信息。 在上传分块时会产生两个文件,一个是文件主体,一个是临时文件。临时文件可以看做是一个数组文件,为每一个分块分配一个值为127的字节。 校验MD5值时会用到两个值: 文件上传状态:只要该文件上传过就不为空,如果完整上传则为true,部分上传返回false; 文件上传地址:如果文件完整上传,返回文件路径;部分上传返回临时文件路径。 /** * 校验文件的MD5 **/ public Result checkFileMd5(String md5) throws IOException { //文件是否上传状态:只要该文件上传过该值一定存在 Object processingObj = stringRedisTemplate.opsForHash().get(UploadConstants.FILE_UPLOAD_STATUS, md5); if (processingObj == null) { return Result.ok("该文件没有上传过"); } boolean processing = Boolean.parseBoolean(processingObj.toString()); //完整文件上传完成时为文件的路径,如果未完成返回临时文件路径(临时文件相当于数组,为每个分块分配一个值为127的字节) String value = stringRedisTemplate.opsForValue().get(UploadConstants.FILE_MD5_KEY + md5); //完整文件上传完成是true,未完成返回false if (processing) { return Result.ok(value,"文件已存在"); } else { File confFile = new File(value); byte[] completeList = FileUtils.readFileToByteArray(confFile); List<Integer> missChunkList = new LinkedList<>(); for (int i = 0; i < completeList.length; i++) { if (completeList[i] != Byte.MAX_VALUE) { //用空格补齐 missChunkList.add(i); } } return Result.ok(missChunkList,"该文件上传了一部分"); } } 说到这,你肯定会问:当这个文件的所有分块上传完成之后,该怎么得到完整的文件呢?接下来我们就说一下分块合并的问题。 分块上传、文件合并 上边我们提到了利用文件的md5值来维护分块和文件的关系,因此我们会将具有相同md5值的分块进行合并,由于每个分块都有自己的索引值,所以我们会将分块按索引像插入数组一样分别插入文件中,形成完整的文件。 分块上传时,要和前端的分块大小、分块数量、当前分块索引等对应好,以备文件合并时使用,此处我们采用的是磁盘映射的方式来合并文件。 //读操作和写操作都是允许的 RandomAccessFile tempRaf = new RandomAccessFile(tmpFile, "rw"); //它返回的就是nio通信中的file的唯一channel FileChannel fileChannel = tempRaf.getChannel(); //写入该分片数据 分片大小 * 第几块分片获取偏移量 long offset = CHUNK_SIZE * multipartFileDTO.getChunk(); //分片文件大小 byte[] fileData = multipartFileDTO.getFile().getBytes(); //将文件的区域直接映射到内存 MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, offset, fileData.length); mappedByteBuffer.put(fileData); // 释放 FileMD5Util.freedMappedByteBuffer(mappedByteBuffer); fileChannel.close(); 每当完成一次分块的上传,还需要去检查文件的上传进度,看文件是否上传完成。 RandomAccessFile accessConfFile = new RandomAccessFile(confFile, "rw"); //把该分段标记为 true 表示完成 accessConfFile.setLength(multipartFileDTO.getChunks()); accessConfFile.seek(multipartFileDTO.getChunk()); accessConfFile.write(Byte.MAX_VALUE); //completeList 检查是否全部完成,如果数组里是否全部都是(全部分片都成功上传) byte[] completeList = FileUtils.readFileToByteArray(confFile); byte isComplete = Byte.MAX_VALUE; for (int i = 0; i < completeList.length && isComplete == Byte.MAX_VALUE; i++) { //与运算, 如果有部分没有完成则 isComplete 不是 Byte.MAX_VALUE isComplete = (byte) (isComplete & completeList[i]); } accessConfFile.close(); 然后更新文件的上传进度到Redis中。 //更新redis中的状态:如果是true的话证明是已经该大文件全部上传完成 if (isComplete == Byte.MAX_VALUE) { stringRedisTemplate.opsForHash().put(UploadConstants.FILE_UPLOAD_STATUS, multipartFileDTO.getMd5(), "true"); stringRedisTemplate.opsForValue().set(UploadConstants.FILE_MD5_KEY + multipartFileDTO.getMd5(), uploadDirPath + "/" + fileName); } else { if (!stringRedisTemplate.opsForHash().hasKey(UploadConstants.FILE_UPLOAD_STATUS, multipartFileDTO.getMd5())) { stringRedisTemplate.opsForHash().put(UploadConstants.FILE_UPLOAD_STATUS, multipartFileDTO.getMd5(), "false"); } if (!stringRedisTemplate.hasKey(UploadConstants.FILE_MD5_KEY + multipartFileDTO.getMd5())) { stringRedisTemplate.opsForValue().set(UploadConstants.FILE_MD5_KEY + multipartFileDTO.getMd5(), uploadDirPath + "/" + fileName + ".conf"); } } 回复break可获取完整源码呦! 以上就是今天的全部内容了,如果你有不同的意见或者更好的idea,欢迎联系阿Q,添加阿Q可以加入技术交流群参与讨论呦!

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

“手撕”BP算法——使用MATLAB搭建简单的神经网络(附代码)

之前一直都是直接使用深度学习的框架,但对里面所涉及到的基本算法却没有深入研究。看了吴恩达的机器学习视频之后,决定使用MATLAB实现一个简单的神经网络,深刻体会到只有用代码从头实现一个算法,才会对这个算法理解得更加深刻,也才能真正掌握该算法。 机器学习定义如下:一个程序被认为能从经验E中学习,解决任务T,达到性能P,当且仅当,有了经验E之后,经过度量P的评判,程序在处理T的性能有所提升。 神经网络是机器学习中的一类算法,在训练过程中,神经网络内部不断地调整其内部参数的大小,使得神经网络的输出不断地向标签靠拢。其中,“调整参数大小”这个过程一般是“梯度下降”,在一个多层的神经网络中,要达到“梯度下降”这个目的,就不得不提一下反向传播算法(Backpropagation Algorithm, BP)。 BP算法的核心就是链式求导法则,这里对BP算法的理论推导如下,主要参考[1]-[3]。 如图所示,考虑一个有三层、每层有两个神经元的神经网络: 上图为决策面可视化结果,蓝色为判为(0,1)与(1,0)的区域,红色为判为(0,0),(1,1)的区域。 上图为修改损失函数为交叉熵时的结果,学习率同样为0.5,可以看出在400次左右便达到收敛,因此损失函数的设计对整个神经网络的参数调整至关重要,至于为什么将损失函数调整为交叉熵后,收敛速度会增加,可自行查阅资料,这里给出一个结论:当输出层采用线性激活函数时,损失函数采用MSE会收敛更快;当输出层采用sigmoid函数时,一般采用交叉熵会收敛更快,且不容易陷入局部最优的情况。 此外,实验表明,学习率对神经网络的收敛也有比较大的影响。当学习率较大时,有可能收敛较快,当然也可能根本不收敛;当学习率较小时,容易进入局部最小值,且收敛较慢。还有就是网络深度不够或者神经元节点较少时,难以训练出较复杂的判决面(例如程序中想训练出一个圆形判决面,但是效果不好)。 如有精通矩阵的同学,可尝试推导一下损失函数对每个参数求偏导的矩阵求导写法。 接下来是“talk is cheap, show you the code”环节。 MATLAB代码及详细注释如下: 主函数:BP_xor_classifier.m clc;close all;clear; %%训练集 %% xor训练集 X = [0,0;0,1;1,0;1,1]'; %一列为一个样本 Y = [1,0;0,1;0,1;1,0]'; %标签一列为一个标签onehot编码后的 %%圆训练集(网络太浅,分类效果不好) % train_number = 400; % r_small = rand(1,train_number);%分布在小圆内的100个点 % r_big = r_small + rand(1,train_number); % angle_small = 2 * pi *rand(1,train_number); % angle_big = 2 * pi * rand(1,train_number); % small(1,:) = r_small .* cos(angle_small); % small(2,:) = r_small .* sin(angle_small); % small(3:4,:) = [ones(1,size(r_small,2));zeros(1,size(r_small,2))]; % big(1,:) = r_big .* cos(angle_big); % big(2,:) = r_big .* sin(angle_big); % big(3:4,:) = [zeros(1,size(r_small,2));ones(1,size(r_small,2))]; % joint_temp = [small,big]; % rerank_idx = randperm(size(joint_temp,2)); % joint = joint_temp(:,rerank_idx); % X = joint(1:2,:); % Y = joint(3:4,:); %%参考博客数据:测试用 % Y = [0.01;0.99]; % X = [0.05;0.1]; % theta_layer1 = [0.35,0.15,0.2;0.35,0.25,0.3]; % theta_layer2 = [0.6,0.4,0.45;0.6,0.5,0.55]; %%学习率和训练次数 learning_rate = 0.1; iter = 10000; %%搭建前向传播网络,初始化网络 layer1_unit = 2;%各层神经元的个数第一层为输入层,最后一层为输出层 layer2_unit = 2; layer3_unit = 2; %参数初始化 theta_layer1 = rand(layer2_unit,layer1_unit+1);%即M*N的随机矩阵,行数为下一层的神经元个数,一行为一组参数,即(b1,w1,w2) theta_layer2 = rand(layer3_unit,layer2_unit+1);%列数为前一层神经元个数+1(加上权重的参数1) for i = 1:iter for j = 1:size(X,2)%一列为一个样本,一个一个遍历该样本 %%搭建前馈式网络 %输入层 out_layer1 = [ones(1,size(X(:,j),2));X(:,j)];%往X的第一行插入一行1,即权重b的系数,用于后面的向量化相乘,这是第一层的输出(隐藏层的输入) %隐藏层 net_H = theta_layer1 * out_layer1; out_H = sigmoid(net_H); out_H_plus1 = [ones(1,size(out_H,2));out_H];%加一行,同上 %输出层 net_O = theta_layer2 * out_H_plus1; out_O = sigmoid(net_O); %%反向传播 %损失函数为MSE,即loss = (Y - out_O) .^ 2 / 2 %%首先求损失对theta_layer2求偏导(链式求导法则) %diff(loss/theta_layer2) = diff(loss/out_O) * diff(out_O/net_O) *diff(net_O/theta_layer2) %即损失对网络输出求导*网络输出对未激活的输出求导*未激活的输出对theta_layer2求导 %diff(loss/out_O) = -(Y - out_O ) %diff(out_O/net_O) = diff(sigmoid(net_O)) %diff(net_O/theta_layer2) = out_H_plus1 % theta_layer2_der = -(Y(:,j) - out_O) .* sigmoid_der(net_O) *out_H_plus1'; %上式中,diff(net_O/theta_layer2)(即out_H_plus1)中每一列的列向量, %分别是net_O对theta_layer2求的偏导结果 %这里做转置的原因是:前两项矩阵元素相乘的结果(2*1), %每一项都要与out_H_plus1(3*1)中的元素相乘,得到一个2*3的矩阵 %%上式为矩阵写法,这里,可改写为以下程序更容易理解 % temp = -(Y(:,j) - out_O) .* sigmoid_der(net_O); % for m = 1:size(temp,1) % theta_layer2_der(m,:) = temp(m) * out_H_plus1; % end %%接下来对theta_layer1求偏导(同理,链式求导法则) %diff(loss/theta_layer1) = diff(loss/out_H) * diff(out_H/net_H) *diff(net_H/theta_layer1) %diff(loss/out_H) = diff(loss/out_O) * diff(out_O/net_O) *diff(net_O/out_H_plus1) %上式中,与diff(loss/theta_layer2)的区别只是最后一项是对out_H_plus1求的偏导 %diff(net_O/out_H_plus1) = theta_layer2',矩阵乘法求导,Y = AX,则A需要转置 %上式中,diff(net_O/out_H_plus1)的结果包含偏置项的系数,在反向传播中不需要,将其舍去(结果的第一行) %diff(out_H/net_H) = diff(sigmoid(net_H)) %diff(net_H/theta_layer1) = out_layer1 % theta_layer1_der = theta_layer2(:,2:end)' * (-(Y(:,j) - out_O)) .*sigmoid_der(net_O) ... % .* sigmoid_der(net_H) *out_layer1'; %%另外的写法:记每一层的误差为Delta %diff(loss/net_O) = diff(loss/out_O) * diff(out_O/net_O) % Delta_3 = -(Y(:,j) - out_O) .* sigmoid_der(net_O); %损失函数为MSE时 Delta_3 = (out_O - Y(:,j))./((1 - out_O) .* out_O) .* sigmoid_der(net_O);%损失函数为交叉熵 %diff(loss/net_H) = diff(loss/out_O) * diff(out_O/net_O) *diff(net_O/out_H_plus1) * diff(out_H_plus1/net_H) %这里,需要将偏置那一项去掉,即diff(net_O/out_H_plus1) = theta_layer2(:,2:end)' Delta_2 = theta_layer2(:,2:end)' * Delta_3 .* sigmoid_der(net_H); theta_layer2_der = Delta_3 * out_H_plus1'; theta_layer1_der = Delta_2 * out_layer1'; %%更新参数 theta_layer2 = theta_layer2 - learning_rate * theta_layer2_der; theta_layer1 = theta_layer1 - learning_rate * theta_layer1_der; %注:这里两种方法计算所得的theta_layer1在小数点后第6位不相同,推测是由于软件精度所致 end %%计算损失并作图动态显示 % loss(i) = sum((Y(:,j) - out_O) .^ 2 / 2); loss(i) = sum(-Y(:,j) .* log(out_O) - (1 - Y(:,j)) .* log(1 - out_O)); % figure(1) % plot(i,loss,'<'); % drawnow; % hold on; end %%做预测 a = 0:0.01:1; b = 0:0.01:1; X = []; for i = 1:size(a,2) for j = 1:size(b,2) temp = [a(i);b(j)]; X = [X,temp]; end end % X = [0.7,10]'; for j = 1:size(X,2) out_layer1 =[ones(1,size(X(:,j),2));X(:,j)]; %隐藏层 net_H = theta_layer1 *out_layer1; out_H = sigmoid(net_H); out_H_plus1 =[ones(1,size(out_H,2));out_H];%加一行,同上 %输出层 net_O = theta_layer2 *out_H_plus1; predict(:,j) = sigmoid(net_O); end %%作图查看判决面(分类面) figure(1) plot(loss);%损失随迭代次数的变化 [~,c2]=max(predict); class_one_idx = find(c2 == 1); class_two_idx = find(c2 == 2); class_one = X(:,class_one_idx); class_two = X(:,class_two_idx); figure(2) plot(class_one(1,:),class_one(2,:),'ro'); hold on; plot(class_two(1,:),class_two(2,:),'b<'); sigmoid函数:sigmoid.m function a = sigmoid(X) for i = 1:size(X,1) for j = 1:size(X,2) a(i,j) = 1/(1+exp(-X(i,j)));%激活函数 end end end sigmoid的导数:sigmoid_der.m %%此函数为sigmoid的导数 function a = sigmoid_der(x) a = zeros(size(x)); for i = 1:size(x,1) a(i,:) = sigmoid(x(i,:)) *(eye(size(x,2)) - diag(sigmoid(x(i,:)))); end 参考: [1]https://mattmazur.com/2015/03/17/a-step-by-step-backpropagation-example/?spm=a2c4e.10696291.0.0.358f19a4xonKKs. [2]https://blog.csdn.net/zhaomengszu/article/details/77834845 [3]吴恩达机器学习视频

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

使用 Mono.Cecil 辅助 Unity3D 手游进行性能测试

Unity3D 引擎在 UnityEngine 名字空间下,提供了 Profiler 类(Unity 5.6 开始似乎改变了这个名字空间),用于辅助对项目性能进行测试。以 Android 平台为例,在构建之前,需要在 Unity 的 File/Build Settings 菜单项弹出的窗口中,勾选 Development Build 一项。后用 adb forward 的方式,将 Android 设备的 TCP 输出转发到电脑,实现和 Unity Profiler 的连接(网上很容易找到这个过程的具体描述,如这里)。但是 Unity Profiler 默认只提供部分方法/函数,尤其是 Unity 内置方法/函数的性能采样,如果想 Profile 自己项目的代码段,就必须在代码段入口和出口加上:方法出口注入代码稍微有些麻烦。尽管 IL 级别的函数都是以一个返回指令结束的,但直接在返回指令之前插入新的指令是不够的。因为很多时候,返回指令是由跳转指令直接跳转过去的。而对于我们在 C# 中获取的指令容器,跳转指令保存了其跳转目标的引用。因此,我们不仅需要在返回指令前插入我们需要的指令(对 Profiler.EndSample 包装方法的调用),还要将跳转目标为该返回指令的跳转指令的目标,修改为我们新增的指令。这里有详尽的关于 IL 指令的列表。对应 Cecil 中 OpCodes 类中的常量,我们可以过滤出跳转指令,并用 Operand 属性获取或修改其跳转目标。修改完成后,需要对当前的模块对象 moduleDef 调用 moduleDef.Write(assemblyPath, new WriterParameters { WriteSymbols = true }) 来写回程序集文件。这个调用中,第二个参数的含义,是把新增的符号也写入程序集(比如我们调用的该程序集之外的方法)。在注入完成后,继续 Android 平台的原生构建生成 apk 包,安装进设备,将设备连接电脑,即可在 Unity 的 Profiler 窗口中看到新增的性能采样信息。

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

当红架构Cloud Native,怎么搭建才能成为上云助攻手?

作者:陈谔,网易云基础服务总经理,现负责网易云计算平台产品线建设,对分布式系统设计开发、云计算平台系统架构有一定的经验和理解。近年来致力于带领团队推进公司开发技术栈的标准化、工具化。 网易云基础服务团队:网易云基础服务拥有优质的硬件资源,经验丰富的研发运维团队,为各类客户提供IaaS、PaaS服务。同时深度整合Docker与Kubernetes技术,打造专业的容器服务。 如何让云成为业务成功的基石而不是障碍,是技术团队需要不断思考的问题,Cloud-Native正是一种让业务技术架构向云而生,充分利用云特性的技术理念与方法论。在近期网易云技术布道系列活动中,网易云基础服务总经理陈谔带来了如何从0到1实践Cloud-Native的精彩分享。 一、什么是Cloud Native? 说到Cloud Native,国内大多数都翻译成云原生,就是让云成为成功的基石,而不是障碍。陈谔对于为什么要实现云原生应用深有体会,网易从2012年开始实施云化的战略,当第一版云计算平台建好的时候,开始引导公司的项目逐渐向云迁移。这个过程中就遇到了一个问题:用上云之后,并没有变得效率奇高,甚至有些项目的效率反而有所下降,大家都有很多抱怨。 从那时陈谔就有一个想法,云计算怎样才能成为公司和开发团队成功的基石,而不是用上云之后给你制造麻烦。他认为要做到这一点首先要理解云的优势,规避云的弱点;另一方面要充分利用云的各层能力,帮助你去成功。所以云原生就是采用适合云端的软件架构和研发模式去做这个事情。 二、如何实践云原生? 关于如何实践云原生,陈谔为大家分享了一些建议。假设大家不是类似BAT这样规模的公司,或者有非常强大的IT团队,在选择技术路线时,陈谔建议大家使用公有云,为什么呢? 1、使用公有云 弹性 首先,使用公有云起步的成本非常低,不需要你去租机房、买物理机,每个月几百块钱就可以起步了。如果你成功了,在爆发性增长时,公有云也有足够大的资源弹性帮助你从一台Scale到几百台,而不需要临时去买服务器。 网络质量 另一方面,由于公有云的规模化效应,网络质量是自建不可比拟的: 有些公有云出入口的带宽很大,甚至有些互联网大厂的公有云平台,用的基础设施跟公司整体业务是一体的; 带宽大的另一个好处是可以抵御DDoS和CC攻击; 其次,公有云有更强的排障能力。国内的国情,网络故障是非常难以排查的,需要有专门的IT团队才能做好。 Managed Cloud Service 云计算有数据库、中间件这些服务,并且不需要你去关注高可用部署、故障恢复、扩缩容等系统层面的运维,操作系统内核级掌控、中间件源码级维护也均由云提供商负责,并且有明确的SLA保障。 高可用保障 此外,云计算可以帮你做高级别的高可用保障。日常的高可用保障,比如双机热备也好,冷备也好,都比不过公有云提供的多可用区的保障。云的多可用区至少是IDC级别的,在一个可用区内就像一张大网一样,至少保证三层的连接,保证你的业务都是互通的,整体架构不用考虑跨机房的问题。 云还有多Region的保障,有一些公司会做异地多活的架构,当然这对业务的侵入性是很大的,但至少可以用多Region的设施,来做数据的灾备。 另外,云的进化速度很快,会持续地更新,现在大多数都是基于Linux的技术栈,可能会不时地出现bug或安全漏洞,如果自己去跟进是非常困难的,公有云一般都会有专业的团队,及时跟进和修复这些安全问题,又省下了用户一笔人员开销。 公有云的取舍 当然,公有云要支持这么大规模的用户,本身有一定的取舍。 1)Design For Failure:公有云倾向于更快失败(影响范围受控)、更快恢复。如果你用的是物理机,出现问题时你会关注这个物理机是不是还“活着”。而公有云如果发现一台机器挂了,会直接进行服务迁移和重启,因为公有云本身有SLA的承诺,为了保证系统的鲁棒性,会更快地把这些疑似故障的节点排除掉。 2)由于公有云这样的特性,日常业务必须结合公有云能力实施高可用架构: 一方面以可用域为基础,实现高可用; 另一方面,将数据状态和业务逻辑分离,如果业务被迁移走了,只要挂上原来的盘就可以恢复了; 节点可重启或重建。 3)Design For Scale:虚拟化性能稍弱于物理机,公有云更追求交付的性能指标的稳定,避免租户业务间的影响,支持业务做Scale。对于开发者来说: 一方面,要知道你采购的磁盘、网络能够提供的性能是什么,根据这些QoS指标去做容量的规划; 另一方面要基于负载均衡、集群管理等能力去做Scale Out,而不是让机器规格越变越大。 2、项目工程化 除了上面提到的基础设施,在项目的工程化方面,陈谔也为大家带来了一些启示。他认为项目工程化是研发协作与云端运维的基础,也是很多团队在起步时可能会忽视的事情。项目的整个流程中,开发、测试、发布的每一步都涉及到公司内角色之间的协作,如果这些步骤做得不流畅,每一个环节的衔接非常困难,效率就会变的非常低,所以项目工程化是对高效构建、发布、运行流程的支持。 合理的版本控制工具 那么,如何做到项目的工程化呢?首先要选择合理的版本控制工具与策略: Git是社区和业界公认的一个比较好的工具; 建议每个应用采用单一的Codebase(12Factors-1: Codebase),把整个开发,构建,发布的流程串联起来,不至于拉下一个base还要决定这里面的代码哪一部分要拿去构建。 常见的版本控制策略包括: 基于Merge的多分支策略,这种模式和多人协作的方式是匹配的,可以看到大家协作产生的代码从分支到合并的过程,但是分支很多也造成了管理的复杂度很高; 如果团队能切割得比较小,功能比较集中的话,可以采用基于Rebase的单Master分支策略,它没有Merge信息,管理起来比较简单。 基于配置的依赖管理 然后可以去做基于配置的依赖管理: 声明依赖(Maven等),而不是把你的软件包全拷贝在代码库下面,实现自动构建 建议声明所有的依赖,包括运行环境的初始化,不隐式依赖系统库(12Factors-2: Dependencies) 接下来要合理拆分模块,可以按业务拆分模块,同时实现公共代码的模块化。 使用Docker实现环境一致性 之前在网易,对稳定性要求很高的产品,其发布流程通常都很曲折,主要原因在于环境的不一致。陈谔的建议是使用Docker实现环境的一致性,Docker容器完整虚拟化了Linux操作系统,将业务代码与运行环境装箱为Docker容器发布到生产环境,差异仅仅为外部注入的配置(如数据库地址等),容器内部文件在开发环境一旦发布则不再变化,从而保证开发环境与生产环境一致。 3、服务化的思维 工程化是做业务架构,建立一个高效团队的基础,接下来要考虑的就是服务化的思维。微服务是当下很流行的概念,采用微服务确实能为应用的迭代和架构带来很多好处。但服务化的架构会带来额外的负担,如果一个项目还处在初期阶段,我们的建议则是服务化思维先于服务化架构。 运维成本:一旦服务多了,环境搭建、故障诊断、运维的工作量都会成倍增加; 服务拆分之后,各个服务间的生命周期是不一致的,要做生命周期的分离,就需要处理更多的异常。服务间存在更多的约束,还是异步的,如依赖关系、版本,要保证消息能够可靠地到达那里; 另一方面,还会有分布式事务的问题,虽然解决起来不难,但是会侵入你的业务。 虽然业务初期,不适合服务化,但应该为后续的服务化做一些准备,否则后面想拆分的时候会变得非常困难: 提取Service API,理解业务中的服务抽象; 数据库设计的时候就考虑服务的划分; 避免跨服务事务,对跨服务事务进行标记; 如果项目发展起来,遇到的第一个问题通常是数据库会挂掉,所以在业务初期就做分库分表是很有必要的; 选择事务支持更好的数据库,如果你用缺乏事务支持的数据库做业务的后端,当你要做服务化拆分或分布式事务的时候,可能会比用MySQL的痛苦很多。 4、实施微服务 随着业务的壮大,是否要采用微服务,就要去衡量微服务带来的收益是否大于成本? 收益 控制迭代更新的影响域,而单体架构很难评估patch的影响范围; 加速迭代,提交代码心里负担小,迭代也能加快; 隔离局部故障; 防止代码架构层面的腐化,比如开发过程中为了赶进度,可能会把原有的架构推倒重来。如果用微服务架构,最多只需要将自己负责的那个模块重新设计。 成本 更多的依赖(eg: ZooKeeper,MQ),要做一个注册中心; 运维复杂度,几十个服务发布更新,运维的复杂度必然会上升; 技术实现的侵入性,在这个过程中难免要用到一些微服务化的框架,虽然对代码的侵入性不大,但对架构的侵入性还是不可避免的。 降低实施成本 良好的工程化,不要给运维的工作带来很多困难; 使用基于云端托管的PaaS服务; 使用基于云端托管的编排服务,帮你去做集群化的运维和管理的工作。 基于Kubernetes简化微服务实施 利用基于Kubernetes的基础设施可以简化微服务,一方面Kubernetes提供了基于域名的服务发现: 使用VIP+域名暴露服务:对比“注册中心”,采用域名服务具有更小的侵入性,更少的依赖 支持名称空间隔离,简化测试环境部署 Kubernetes还可以做基于iptables的透明RPC分发: 无需在程序中访问注册中心获取成员列表进行软负载均衡; 无需内网负载均衡层次增加网络开销。 比如,服务A访问服务B的虚拟IP VIP,利用iptables做DNAT,转成B中的所有成员,服务A可以直接,并利用probability特性按权重分发请求,比域名做轮转的负载均衡效果要好,因为iptables可控,域名不可控。 用Kubernetes还可以让你获得自动化运维能力: 自动扩缩容 自动故障处理(重试、迁移) 自动化滚动更新,通过健康检查与滚动的配合实现无缝更新 还可以基于Service 抽象实现蓝绿发布 Kubernetes以解耦的基础服务层的方式提供了对服务化的支持,避免了代码实现层面的耦合,通过云端托管Kubernetes服务能够将实现服务化的成本大幅降低。而且Kubernetes对业务没有侵入性,实现服务化的代价相对会比较小,后面业务变得非常重,需要细粒度控制时,再用到其它框架也没有什么影响。 我们深度整合了Docker技术和Kubernetes集群编排技术,所以网易云中会有一个Kubernetes Master,所有租户的业务都可以使用这个Master,不用用户自己维护。 5、DevOps 前面讲到的都是云原生相关的技术,实际上实现云原生还需要一些研发、运维和组织架构上的方式调整,比如DevOps。DevOps的出现是为了解决运维角色与开发角色的矛盾,运维追求的是可用率优先,而开发希望应用能快速更新迭代。 DevOps 与微服务 微服务架构能够支持更高频的迭代,降低更新迭代的风险,这与DevOps的目标是一致;但是微服务架构也会给运维带来成倍的工作量,可基于DevOps分散运维操作,而不是集中依赖少量运维角色。 实施DevOps 实施DevOps需要CI/CD、编排、故障诊断等工具链的支持,同时需要运维实现从操作到审计的职能转换,运维工作前置,在前期和开发团队合作。很多运维还需要开发工具,提高运转效率。 基于DevOps工具链支持微服务架构 1)Jenkins-容器-镜像仓库-服务编排 Pipeline as Code:实施服务化后持续集成的复杂度成倍增加,需要定义大量的流程,包含大量Jobs,以代码的方式管理Pipeline能够支持审计,有效管理复杂性并降低维护成本。 2)日志服务-分布式跟踪系统-性能管理服务 日志服务:Kafka+ELK套件,以网易云为例自动完成容器日志收集,并提供订阅接口可对接ELK。 分布式跟踪系统:在微服务架构下必须要做到与单体架构同样的服务请求的调用路径跟踪能力,才能够有效定位故障。可参考的框架有Zipkin,需要对RPC框架等做instrumentation,在调用过程中携带额外的头信息。 性能管理服务:微服务架构下依赖关系复杂,发生性能问题时难以定位源头及影响范围,性能管理服务可提供调用关系拓扑,及时统计慢响应及错误响应,有利于发现性能问题与定位故障。以网易云为例,利用Kubernetes提供元信息,利用AOP对常用库做instrumentation,可在无须配置及侵入代码的情况下,自动绘制拓扑,分析性能。 下图是我们内部性能管理的拓扑截图: 三、总结 最后,陈谔将云原生架构实现的要点总结如下,希望能给云计算的用户带来有价值的参考: 使用公有云; 重视项目工程化; 项目起步时建立服务化思维,而不要急于采用服务化架构带来不必要的负担; 实施微服务需权衡收益与成本,基于Kubernetes可简化微服务实施; DevOps能与微服务架构良好匹配,但实施DevOps需要完善的工具链支持。 原文发布时间为:2017-03-31 本文来自云栖社区合作伙伴DBAplus

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

用 WorkBuddy + 福昕 MCP,手搓一个属于自己的 Skill(一)

摘要:模板给的 Skill 总差一口气,真正顺手的工具得自己搓。本文不绕弯子,讲清三件事:Skill(技能包)的本质就是一个 SKILL.md 文件,靠 name description 触发词 + 工作流约定把自己封装出来;再接入福昕 PDF 转 Word MCP(服务名 foxit-pdf365-convert),搓一个「老板甩 PDF 合同 → 自动转 Word + 抽表」的日常专属 Skill;最后教你怎么在描述、触发词、执行约定里加自己的诉求。全程通用范式、零虚构,配 YAML 示例与 MCP 配置,文末埋了续篇。

资源下载

更多资源
腾讯云软件源

腾讯云软件源

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

Nacos

Nacos

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

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册