PHP算法之二分查找

二分查找的定义二分查找也称折半查找(Binary Search),它是一种效率较高的查找方法。但是,折半查找要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列。算法的要求从上面的定义我们可以知道,满足该算法的要求必须如下两点:必须采用顺序存储结构。必须按关键字大小有序排列。算法的步骤其实,二分查找也还是比较容易理解的,大概就是一分为二,然后两边比较,保留有效区间,继续一分为二查找,直到找到或者超出区间则结束,所以二分查找的基本步骤是:确定要查找的区间确定要二分时的参照点区间内选取二分点根据二分点的值,综合左右区间情况以及求解的目的,舍去一半无用的区间继续在有效区间重复上面的步骤算法源码这里,我主要采用递归和非递归两种方法实现,具体如下:首先第一种是非递归的算法实现,算法如下:/** * 二分查找算法 * @param array $arr 待查找区间 * @param int $number 查找数 * @return int 返回找到的键 /function binary_search($arr, $number) { // 非数组或者数组为空,直接返回-1 if (!is_array($arr) || empty($arr)) { return -1; } // 初始变量值 $len = count($arr); $lower = 0; $high = $len - 1; // 最低点比最高点大就退出 while ($lower <= $high) { // 以中间点作为参照点比较 $middle = intval(($lower + $high) / 2); if ($arr[$middle] > $number) { // 查找数比参照点小,舍去右边 $high = $middle - 1; } else if ($arr[$middle] < $number) { // 查找数比参照点大,舍去左边 $lower = $middle + 1; } else { // 查找数与参照点相等,则找到返回 return $middle; } } // 未找到,返回-1 return -1;}然后第二种是递归的算法实现,算法如下:/* * @param array $arr 待查找区间 * @param int $number 查找数 * @param int $lower 区间最低点 * @param int $high 区间最高点 * @return int */function binary_search_recursion(&$arr, $number, $lower, $high) { // 以区间的中间点作为参照点比较 $middle = intval(($lower + $high) / 2); // 最低点比最高点大就退出 if ($lower > $high) { return -1; } if ($number > $arr[$middle]) { // 查找数比参照点大,舍去左边继续查找 return binary_search_recursion($arr, $number, $middle + 1, $high); } elseif ($number < $arr[$middle]) { // 查找数比参照点小,舍去右边继续查找 return binary_search_recursion($arr, $number, $lower, $middle - 1); } else { return $middle; }}算法的使用需求是在一个排列好的区间($arr)中,查找一个数($number)的所在位置,所以,调用算法查找如下:// 待查找区间$arr = [1, 3, 7, 9, 11, 57, 63, 99];// 非递归查找57所在的位置$find_key = binary_search($arr, 57);// 递归查找57所在的位置$find_key_r = binary_search_recursion($arr, 57, 0, count($arr));// 输出打印print_r($find_key);print_r($find_key_r);时间复杂度分析在有序数组中如果用暴力的算法去查找,也就是逐个遍历比较,那么时间复杂度是O(n);但是,用二分查找后,因为每次可以舍去一半查找区间,所以会将时间复杂度减少到O(logn),算法更优。最后又到了无聊的客套话时间,老规律,有问题直接留言,有想法直接说,有错误直接提出来,我都会及时回复的,谢谢。 ...

September 20, 2018 · 1 min · jiezi

什么?强化学习竟然来源于心理学?

欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~本文由罗晖发表于云+社区专栏1. Google的DQN论文2015年2月,Google在Nature上发表了一篇论文(见附件):Human-level control through deep reinforcement learning。文章描述了如何让电脑自己学会打Atari 2600电子游戏。Atari 2600是80年代风靡美国的游戏机,总共包括49个独立的游戏,其中不乏我们熟悉的Breakout(打砖块),Galaxy Invaders(小蜜蜂)等经典游戏。Google算法的输入只有游戏屏幕的图像和游戏的得分,在没有人为干预的情况下,电脑自己学会了游戏的玩法,而且在29个游戏中打破了人类玩家的记录。Google给出的深度络架构图如下:网络的左边是输入,右边是输出。 游戏屏幕的图像先经过两个卷积层(论文中写的是三个),然后经过两个全连接层, 最后映射到游戏手柄所有可能的动作。各层之间使用ReLU激活函数。2. 强化学习(Q-Learning)根据维基百科的描述,强化学习定义如下:强化学习是机器学习中的一个领域,强调如何基于环境而行动,以取得最大化的预期利益。其灵感来源于心理学中的行为主义理论,即有机体如何在环境给予的奖励或惩罚的刺激下,逐步形成对刺激的预期,产生能获得最大利益的习惯性行为。在强化学习的世界里, 算法称之为Agent, 它与环境发生交互,Agent从环境中获取状态(state),并决定自己要做出的动作(action).环境会根据自身的逻辑给Agent予以奖励(reward)。奖励有正向和反向之分。比如在游戏中,每击中一个敌人就是正向的奖励,掉血或者游戏结束就是反向的奖励。2.1. 马尔可夫决策过程现在的问题是,你如何公式化一个强化学习问题,然后进行推导呢?最常见的方法是通过马尔可夫决策过程。假设你是一个代理,身处某个环境中(例如《打砖块》游戏)。这个环境处于某个特定的状态(例如,牌子的位置、球的位置与方向,每个砖块存在与否)。人工智能可以可以在这个环境中做出某些特定的动作(例如,向左或向右移动拍子)。这些行为有时候会带来奖励(分数的上升)。行为改变环境,并带来新的状态,代理可以再执行另一个动作。你选择这些动作的规则叫做策略。通常来说,环境是随机的,这意味着下一状态也或多或少是随机的(例如,当你漏掉了球,发射一个新的时候,它会去往随机的方向)。状态与动作的集合,加上改变状态的规则,组成了一个马尔可夫决策过程。这个过程(例如一个游戏)中的一个情节(episode)形成了状态、动作与奖励的有限序列。其中 si 表示状态,ai 表示动作,ri+1 代表了执行这个动作后获得的奖励。情节以最终的状态 sn 结束(例如,「Game Over」画面)。一个马尔可夫决策过程基于马尔可夫假设(Markov assumption),即下一状态 si+1 的概率取决于现在的状态 si 和动作 ai,而不是之前的状态与动作。2.2. 折扣未来奖励(Discounted Future Reward)为了长期表现良好,我们不仅需要考虑即时奖励,还有我们将得到的未来奖励。我们该如何做呢?对于给定的马尔可夫决策过程的一次运行,我们可以容易地计算一个情节的总奖励:鉴于此,时间点 t 的总未来回报可以表达为:但是由于我们的环境是随机的,我们永远无法确定如果我们在下一个相同的动作之后能否得到一样的奖励。时间愈往前,分歧也愈多。因此,这时候就要利用折扣未来奖励来代替:在这里 是数值在0与1之间的贴现因子——奖励在距离我们越远的未来,我们便考虑的越少。我们很容易看到,折扣未来奖励在时间步骤 t 的数值可以根据在时间步骤 t+1 的相同方式表示:如果我们将贴现因子定义为 =0,那么我们的策略将会过于短浅,即完全基于即时奖励。如果我们希望平衡即时与未来奖励,那么贴现因子应该近似于 =0.9。如果我们的环境是确定的,相同的动作总是导致相同的奖励,那么我们可以将贴现因子定义为 =1。一个代理做出的好的策略应该是去选择一个能够最大化(折扣后)未来奖励的动作。2.3. Q-Learning算法描述:算法中的 是指学习率,其控制前一个 Q 值和新提出的 Q 值之间被考虑到的差异程度。尤其是,当 =1 时,两个 Qs,a 互相抵消,结果刚好和贝尔曼方程一样。我们用来更新 Qs,a 的只是一个近似,而且在早期阶段的学习中它完全可能是错误的。但是随着每一次迭代,该近似会越来越准确;而且我们还发现如果我们执行这种更新足够长时间,那么 Q 函数就将收敛并能代表真实的 Q 值。3. 卷积神经网络(CNN)在图像处理中,往往把图像表示为像素的向量,比如一个1000×1000的图像,可以表示为一个1000000的向量。在上一节中提到的神经网络中,如果隐含层数目与输入层一样,即也是1000000时,那么输入层到隐含层的参数数据为1000000×1000000=10^12,这样就太多了,基本没法训练。所以图像处理要想练成神经网络大法,必先减少参数加快速度。3.1. 局部感知卷积神经网络有两种神器可以降低参数数目,第一种神器叫做局部感知野。一般认为人对外界的认知是从局部到全局的,而图像的空间联系也是局部的像素联系较为紧密,而距离较远的像素相关性则较弱。因而,每个神经元其实没有必要对全局图像进行感知,只需要对局部进行感知,然后在更高层将局部的信息综合起来就得到了全局的信息。网络部分连通的思想,也是受启发于生物学里面的视觉系统结构。视觉皮层的神经元就是局部接受信息的(即这些神经元只响应某些特定区域的刺激)。如下图所示:左图为全连接,右图为局部连接。在上右图中,假如每个神经元只和10×10个像素值相连,那么权值数据为1000000×100个参数,减少为原来的万分之一。而那10×10个像素值对应的10×10个参数,其实就相当于卷积操作。3.2. 参数共享但其实这样的话参数仍然过多,那么就启动第二级神器,即权值共享。在上面的局部连接中,每个神经元都对应100个参数,一共1000000个神经元,如果这1000000个神经元的100个参数都是相等的,那么参数数目就变为100了。怎么理解权值共享呢?我们可以这100个参数(也就是卷积操作)看成是提取特征的方式,该方式与位置无关。这其中隐含的原理则是:图像的一部分的统计特性与其他部分是一样的。这也意味着我们在这一部分学习的特征也能用在另一部分上,所以对于这个图像上的所有位置,我们都能使用同样的学习特征。更直观一些,当从一个大尺寸图像中随机选取一小块,比如说 8x8 作为样本,并且从这个小块样本中学习到了一些特征,这时我们可以把从这个 8x8 样本中学习到的特征作为探测器,应用到这个图像的任意地方中去。特别是,我们可以用从 8x8 样本中所学习到的特征跟原本的大尺寸图像作卷积,从而对这个大尺寸图像上的任一位置获得一个不同特征的激活值。如下图所示,展示了一个3×3的卷积核在5×5的图像上做卷积的过程。每个卷积都是一种特征提取方式,就像一个筛子,将图像中符合条件(激活值越大越符合条件)的部分筛选出来。3.3. 多卷积核上面所述只有100个参数时,表明只有1个10×10的卷积核,显然,特征提取是不充分的,我们可以添加多个卷积核,比如32个卷积核,可以学习32种特征。在有多个卷积核时,如下图所示:上图右,不同颜色表明不同的卷积核。每个卷积核都会将图像生成为另一幅图像。比如两个卷积核就可以将生成两幅图像,这两幅图像可以看做是一张图像的不同的通道。如下图所示,下图有个小错误,即将w1改为w0,w2改为w1即可。下文中仍以w1和w2称呼它们。下图展示了在四个通道上的卷积操作,有两个卷积核,生成两个通道。其中需要注意的是,四个通道上每个通道对应一个卷积核,先将w2忽略,只看w1,那么在w1的某位置(i,j)处的值,是由四个通道上(i,j)处的卷积结果相加然后再取激活函数值得到的。所以,在上图由4个通道卷积得到2个通道的过程中,参数的数目为4×2×2×2个,其中4表示4个通道,第一个2表示生成2个通道,最后的2×2表示卷积核大小。3.4. Down-pooling在通过卷积获得了特征 (features) 之后,下一步我们希望利用这些特征去做分类。理论上讲,人们可以用所有提取得到的特征去训练分类器,例如 softmax 分类器,但这样做面临计算量的挑战。例如:对于一个 96X96 像素的图像,假设我们已经学习得到了400个定义在8X8输入上的特征,每一个特征和图像卷积都会得到一个 (96 − 8 + 1) × (96 − 8 + 1) = 7921 维的卷积特征,由于有 400 个特征,所以每个样例 (example) 都会得到一个 7921 × 400 = 3,168,400 维的卷积特征向量。学习一个拥有超过 3 百万特征输入的分类器十分不便,并且容易出现过拟合 (over-fitting)。为了解决这个问题,首先回忆一下,我们之所以决定使用卷积后的特征是因为图像具有一种“静态性”的属性,这也就意味着在一个图像区域有用的特征极有可能在另一个区域同样适用。因此,为了描述大的图像,一个很自然的想法就是对不同位置的特征进行聚合统计,例如,人们可以计算图像一个区域上的某个特定特征的平均值 (或最大值)。这些概要统计特征不仅具有低得多的维度 (相比使用所有提取得到的特征),同时还会改善结果(不容易过拟合)。这种聚合的操作就叫做池化 (pooling),有时也称为平均池化或者最大池化 (取决于计算池化的方法)。3.5. 多层卷积在实际应用中,往往使用多层卷积,然后再使用全连接层进行训练,多层卷积的目的是一层卷积学到的特征往往是局部的,层数越高,学到的特征就越全局化。4. DQN算法描述单纯的Q-Learning算法使用表来保存状态,一个1000×1000图像的像素状态数基本接近与无穷,故有了CNN+Q-Learning 即DQN算法,算法描述如下:5. 使用DQN训练“接砖块”游戏深度学习的开源类库比较多,比较著名的有tensorlow、caffe等。此处我们使用Tensorflow来训练游戏“接砖块”。游戏截图如下:通过点击鼠标左键、右键控制滑块的左右移动来接住小球,如果球碰到底面,则游戏结束主要python代码如下(游戏本身的代码省略,此处主要关注算法代码):#Game的定义类,此处Game是什么不重要,只要提供执行Action的方法,获取当前游戏区域像素的方法即可class Game(object): def init(self): #Game初始化 # action是MOVE_STAY、MOVE_LEFT、MOVE_RIGHT # ai控制棒子左右移动;返回游戏界面像素数和对应的奖励。(像素->奖励->强化棒子往奖励高的方向移动) def step(self, action):# learning_rateLEARNING_RATE = 0.99# 跟新梯度INITIAL_EPSILON = 1.0FINAL_EPSILON = 0.05# 测试观测次数EXPLORE = 500000 OBSERVE = 500# 记忆经验大小REPLAY_MEMORY = 500000# 每次训练取出的记录数BATCH = 100# 输出层神经元数。代表3种操作-MOVE_STAY:[1, 0, 0] MOVE_LEFT:[0, 1, 0] MOVE_RIGHT:[0, 0, 1] output = 3 # MOVE_STAY:[1, 0, 0] MOVE_LEFT:[0, 1, 0] MOVE_RIGHT:[0, 0, 1]input_image = tf.placeholder(“float”, [None, 80, 100, 4]) # 游戏像素action = tf.placeholder(“float”, [None, output]) # 操作#定义CNN-卷积神经网络def convolutional_neural_network(input_image): weights = {‘w_conv1’:tf.Variable(tf.zeros([8, 8, 4, 32])), ‘w_conv2’:tf.Variable(tf.zeros([4, 4, 32, 64])), ‘w_conv3’:tf.Variable(tf.zeros([3, 3, 64, 64])), ‘w_fc4’:tf.Variable(tf.zeros([3456, 784])), ‘w_out’:tf.Variable(tf.zeros([784, output]))} biases = {‘b_conv1’:tf.Variable(tf.zeros([32])), ‘b_conv2’:tf.Variable(tf.zeros([64])), ‘b_conv3’:tf.Variable(tf.zeros([64])), ‘b_fc4’:tf.Variable(tf.zeros([784])), ‘b_out’:tf.Variable(tf.zeros([output]))} conv1 = tf.nn.relu(tf.nn.conv2d(input_image, weights[‘w_conv1’], strides = [1, 4, 4, 1], padding = “VALID”) + biases[‘b_conv1’]) conv2 = tf.nn.relu(tf.nn.conv2d(conv1, weights[‘w_conv2’], strides = [1, 2, 2, 1], padding = “VALID”) + biases[‘b_conv2’]) conv3 = tf.nn.relu(tf.nn.conv2d(conv2, weights[‘w_conv3’], strides = [1, 1, 1, 1], padding = “VALID”) + biases[‘b_conv3’]) conv3_flat = tf.reshape(conv3, [-1, 3456]) fc4 = tf.nn.relu(tf.matmul(conv3_flat, weights[‘w_fc4’]) + biases[‘b_fc4’]) output_layer = tf.matmul(fc4, weights[‘w_out’]) + biases[‘b_out’] return output_layer#训练神经网络def train_neural_network(input_image): predict_action = convolutional_neural_network(input_image) argmax = tf.placeholder(“float”, [None, output]) gt = tf.placeholder(“float”, [None]) action = tf.reduce_sum(tf.mul(predict_action, argmax), reduction_indices = 1) cost = tf.reduce_mean(tf.square(action - gt)) optimizer = tf.train.AdamOptimizer(1e-6).minimize(cost) game = Game() D = deque() _, image = game.step(MOVE_STAY) image = cv2.cvtColor(cv2.resize(image, (100, 80)), cv2.COLOR_BGR2GRAY) ret, image = cv2.threshold(image, 1, 255, cv2.THRESH_BINARY) input_image_data = np.stack((image, image, image, image), axis = 2) #print (“IMG2:%s” %input_image_data) with tf.Session() as sess: sess.run(tf.initialize_all_variables()) saver = tf.train.Saver() n = 0 epsilon = INITIAL_EPSILON while True: #print(“InputImageData:”, input_image_data) action_t = predict_action.eval(feed_dict = {input_image : [input_image_data]})[0] argmax_t = np.zeros([output], dtype=np.int) if(random.random() <= INITIAL_EPSILON): maxIndex = random.randrange(output) else: maxIndex = np.argmax(action_t) argmax_t[maxIndex] = 1 if epsilon > FINAL_EPSILON: epsilon -= (INITIAL_EPSILON - FINAL_EPSILON) / EXPLORE reward, image = game.step(list(argmax_t)) image = cv2.cvtColor(cv2.resize(image, (100, 80)), cv2.COLOR_BGR2GRAY) ret, image = cv2.threshold(image, 1, 255, cv2.THRESH_BINARY) image = np.reshape(image, (80, 100, 1)) input_image_data1 = np.append(image, input_image_data[:, :, 0:3], axis = 2) D.append((input_image_data, argmax_t, reward, input_image_data1)) if len(D) > REPLAY_MEMORY: D.popleft() if n > OBSERVE: minibatch = random.sample(D, BATCH) input_image_data_batch = [d[0] for d in minibatch] argmax_batch = [d[1] for d in minibatch] reward_batch = [d[2] for d in minibatch] input_image_data1_batch = [d[3] for d in minibatch] gt_batch = [] out_batch = predict_action.eval(feed_dict = {input_image : input_image_data1_batch}) for i in range(0, len(minibatch)): gt_batch.append(reward_batch[i] + LEARNING_RATE * np.max(out_batch[i])) print(“gt_batch:”, gt_batch, “argmax:”, argmax_batch) optimizer.run(feed_dict = {gt : gt_batch, argmax : argmax_batch, input_image : input_image_data_batch}) input_image_data = input_image_data1 n = n+1 print(n, “epsilon:”, epsilon, " " ,“action:”, maxIndex, " " ,“reward:”, reward)train_neural_network(input_image)6. 总结说到这里,相信你已经能对强化学习有了一个大致的了解。接下来的事情,应该是如何把这项技术应用到我们的工作中,让它发挥出应有的价值。问答什么时候使用某种强化学习算法?相关阅读使用 Q-Learning 实现 FlappyBird AI强化学习总结一文学习基于蒙特卡罗的强化学习方法 【每日课程推荐】机器学习实战!快速入门在线广告业务及CTR相应知识 ...

September 20, 2018 · 3 min · jiezi

每天坚持一个CSS——社会人

每天一个CSS-社会人实现效果想法之前看到一篇博客,使用python绘制出了小猪佩奇,所以自己想试一试,采用纯html + CSS绘制出低配版的小猪佩奇。实现思路使用上一篇,圆与边框实现。最后是一个组合图形,初学者都可以完成,可尝试。缺点1、低配版的小猪太圆润,后面会对整体图形进行修改。2、在布局上,没有坚持div嵌套,导致缩放时位置错位,如果想实行缩放一致,可采用小猪的头部嵌套布局实现。3、途中可看到小猪没有穿鞋子。后面的想法1、给小猪穿上鞋子。2、加上小猪行走动画。3、加上小猪手摆动画。想法很重要,但是有想法就去完成,无关事的大小,都会努力去完成。代码地地址https://github.com/webfeat/css3-everyday/blob/master/%E6%AF%8F%E5%A4%A9%E4%B8%80%E4%B8%AAcss/%E5%B0%8F%E7%8C%AA%E4%BD%A9%E5%A5%87.html

September 20, 2018 · 1 min · jiezi

[LeetCode]罗马数字转整数(Roman to Integer)

题目描述罗马数字包含以下七种字符:I, V, X, L,C,D 和 M。字符 数值I 1V 5X 10L 50C 100D 500M 1000例如, 罗马数字 2 写做 II ,即为两个并列的 1。12 写做 XII ,即为 X + II 。 27 写做 XXVII, 即为 XX + V + II 。通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII,而是 IV。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX。这个特殊的规则只适用于以下六种情况:I 可以放在 V (5) 和 X (10) 的左边,来表示 4 和 9。X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90。C 可以放在 D (500) 和 M (1000) 的左边,来表示 400 和 900。给定一个罗马数字,将其转换成整数。输入确保在 1 到 3999 的范围内。示例 1:输入: “III"输出: 3示例 2:输入: “IV"输出: 4示例 3:输入: “IX"输出: 9示例 4:输入: “LVIII"输出: 58解释: C = 100, L = 50, XXX = 30, III = 3.示例 5:输入: “MCMXCIV"输出: 1994解释: M = 1000, CM = 900, XC = 90, IV = 4.解决方法如果当前字符代表的数字小于下一个字符代表的数字,则做减法,反之加法 public int romanToInt(String s) { HashMap<Character, Integer> map = new HashMap<>(); map.put(‘I’, 1); map.put(‘V’, 5); map.put(‘X’, 10); map.put(‘L’, 50); map.put(‘C’, 100); map.put(‘D’, 500); map.put(‘M’, 1000); int sum = 0; for (int i = 0; i < s.length() - 1; i++) { if (map.get(s.charAt(i)) < map.get(s.charAt(i + 1))) sum -= map.get(s.charAt(i)); else sum += map.get(s.charAt(i)); } sum += map.get(s.charAt(s.length() - 1)); return sum; }时间复杂度:O(s),s为字符串的长度空间复杂度:O(1)本文首发:https://lierabbit.cn/2018/05/… ...

September 20, 2018 · 1 min · jiezi

三大图表库:ECharts 、 BizCharts 和 G2,该如何选择?

最近阿里正式开源的BizCharts图表库基于React技术栈,各个图表项皆采用了组件的形式,贴近React的使用特点。同时BizCharts基于G2进行封装,Bizcharts也继承了G2相关特性。公司目前统一使用的是ECharts图表库,下文将对3种图表库进行分析比对。BizCharts文档地址:BizCharts一、安装通过 npm/yarn 引入npm install bizcharts –saveyarn add bizcharts –save二、引用成功安装完成之后,即可使用 import 或 require 进行引用。例子:import { Chart, Geom, Axis, Tooltip, Legend } from ‘bizcharts’;import chartConfig from ‘./assets/js/chartConfig’;<div className=“App”> <Chart width={600} height={400} data={chartConfig.chartData} scale={chartConfig.cols}> <Axis name=“genre” title={chartConfig.title}/> <Axis name=“sold” title={chartConfig.title}/> <Legend position=“top” dy={-20} /> <Tooltip /> <Geom type=“interval” position=“genresold” color=“genre” /> </Chart></div>该示例中,图表的数据配置单独存入了其他js文件中,避免页面太过冗杂module.exports = { chartData : [ { genre: ‘Sports’, sold: 275, income: 2300 }, { genre: ‘Strategy’, sold: 115, income: 667 }, { genre: ‘Action’, sold: 120, income: 982 }, { genre: ‘Shooter’, sold: 350, income: 5271 }, { genre: ‘Other’, sold: 150, income: 3710 } ], // 定义度量 cols : { sold: { alias: ‘销售量’ }, // 数据字段别名映射 genre: { alias: ‘游戏种类’ } }, title : { autoRotate: true, // 是否需要自动旋转,默认为 true textStyle: { fontSize: ‘12’, textAlign: ‘center’, fill: ‘#999’, fontWeight: ‘bold’, rotate: 30 }, // 坐标轴文本属性配置 position:‘center’, // 标题的位置,新增 }}效果预览:三、DataSetBizCharts中可以通过dataset(数据处理模块)来对图标数据进行处理,该方法继承自G2,在下文中将对此进行详细分析。快速跳转G2BizCharts基于G2进行开发,在研究BizCharts的过程中也一起对G2进行了实践。一、安装和BizCharts一样,可以通过 npm/yarn 引入npm install @antv/g2 –saveyarn add @antv/g2 –save与BizCharts不同,G2初始化数据并非以组件的形式引入,而是需要获取需要在某个DOM下初始化图表。获取该DOM的唯一属性id之后,通过chart()进行初始化。二、引用示例:import React from ‘react’;import G2 from ‘@antv/g2’; class g2 extends React.Component {constructor(props) { super(props); this.state = { data :[ { genre: ‘Sports’, sold: 275 }, { genre: ‘Strategy’, sold: 115 }, { genre: ‘Action’, sold: 120 }, { genre: ‘Shooter’, sold: 350 }, { genre: ‘Other’, sold: 150 } ] }; } componentDidMount() { const chart = new G2.Chart({ container: ‘c1’, // 指定图表容器 ID width: 600, // 指定图表宽度 height: 300 // 指定图表高度 }); chart.source(this.state.data); chart.interval().position(‘genresold’).color(‘genre’); chart.render(); } render() { return ( <div id=“c1” className=“charts”> </div> ); }}export default g2;效果图:三、DataSetDataSet 主要有两方面的功能,解析数据(Connector)&加工数据(Transform)。官方文档描述得比较详细,可以参考官网的分类:源数据的解析,将csv, dsv,geojson 转成标准的JSON,查看Connector加工数据,包括 filter,map,fold(补数据) 等操作,查看Transform统计函数,汇总统计、百分比、封箱 等统计函数,查看 Transform特殊数据处理,包括 地理数据、矩形树图、桑基图、文字云 的数据处理,查看 Transform// step1 创建 dataset 指定状态量const ds = new DataSet({ state: { year: ‘2010’ }});// step2 创建 DataViewconst dv = ds.createView().source(data);dv.transform({ type: ‘filter’, callback(row) { return row.year === ds.state.year; }});// step3 引用 DataViewchart.source(dv);// step4 更新状态量ds.setState(‘year’, ‘2012’);以下采用官网文档给出的示例进行分析 示例一该表格里面的数据是美国各个州不同年龄段的人口数量,表格数据存放在类型为CVS的文件中数据链接(该链接中为json类型的数据)State小于5岁5至13岁14至17岁18至24岁25至44岁45至64岁65岁及以上WY3825360890293145398013733814727965614DC3635250439252257556919355714004370648VT3263562538337576167915541918859386649……………………初始化数据处理模块import DataSet from ‘@antv/data-set’;const ds = new DataSet({//state表示创建dataSet的状态量,可以不进行设置 state: { currentState: ‘WY’ }});const dvForAll = ds// 在 DataSet 实例下创建名为 populationByAge 的数据视图 .createView(‘populationByAge’) // source初始化图表数据,data可为http请求返回的数据结果 .source(data, { type: ‘csv’, // 使用 CSV 类型的 Connector 装载 data,如果是json类型的数据,可以不进行设置,默认为json类型});/**trnasform对数据进行加工处理,可通过type设置加工类型,具体参考上文api文档加工过后数据格式为[{state:‘WY’,key:‘小于5岁’,value:38253},{state:‘WY’,key:‘5至13岁’,value:60890},]/ dvForAll.transform({ type: ‘fold’, fields: [ ‘小于5岁’,‘5至13岁’,‘14至17岁’,‘18至24岁’,‘25至44岁’,‘45至64岁’,‘65岁及以上’ ], key: ‘age’, value: ‘population’});//其余transform操作const dvForOneState = ds .createView(‘populationOfOneState’) .source(dvForAll); // 从全量数据继承,写法也可以是.source(‘populationByAge’) dvForOneState .transform({ // 过滤数据,筛选出state符合的地区数据 type: ‘filter’, callback(row) { return row.state === ds.state.currentState; }}) .transform({ type: ‘percent’, field: ‘population’, dimension: ‘age’, as: ‘percent’ });使用G2绘图G2-chart Api文档import G2 from ‘@antv/g2’;// 初始化图表,id指定了图表要插入的dom,其他属性设置了图表所占的宽高const c1 = new G2.Chart({ id: ‘c1’, forceFit: true, height: 400,});// chart初始化加工过的数据dvForAllc1.source(dvForAll);// 配置图表图例c1.legend({ position: ’top’,});// 设置坐标轴配置,该方法返回 chart 对象,以下代码表示将坐标轴属性为人口的数据,转换为M为单位的数据c1.axis(‘population’, { label: { formatter: val => { return val / 1000000 + ‘M’; } }});c1.intervalStack() .position(‘statepopulation’) .color(‘age’) .select(true, { mode: ‘single’, style: { stroke: ‘red’, strokeWidth: 5 } }); //当tooltip发生变化的时候,触发事件,修改ds的state状态量,一旦状态量改变,就会触发图表的更新,所以c2饼图会触发改变c1.on(’tooltip:change’, function(evt) { const items = evt.items || []; if (items[0]) { //修改的currentState为鼠标所触及的tooltip的地区 ds.setState(‘currentState’, items[0].title); }});// 绘制饼图const c2 = new G2.Chart({ id: ‘c2’, forceFit: true, height: 300, padding: 0,});c2.source(dvForOneState);c2.coord(’theta’, { radius: 0.8 // 设置饼图的大小});c2.legend(false);c2.intervalStack() .position(‘percent’) .color(‘age’) .label(‘age*percent’,function(age, percent) { percent = (percent * 100).toFixed(2) + ‘%’; return age + ’ ’ + percent; });c1.render();c2.render();EChartsECharts是一个成熟的图表库, 使用方便、图表种类多、容易上手。文档资源也比较丰富,在此不做赘述。ECharts文档ECharts & BizCharts & G2 对比对比BizCharts和G2两种图表库,BizCharts主要是进行了一层封装,使得图表可以以组件的形式进行调用,按需加载,使用起来更加方便。简单对比一下三个图表库的区别:初始化图表:ECharts:// 基于准备好的dom,初始化ECharts实例var myChart = echarts.init(document.getElementById(‘main’));BizCharts:// 以组件的形式,组合调用import { Chart, Geom, Axis, … } from ‘bizcharts’;<Chart width={600} height={400} data={data}> …</Chart>G2:// 基于准备好的dom,配置之后进行初始化const chart = new G2.Chart({ container: ‘c1’, // 指定图表容器 ID width: 600, // 指定图表宽度 height: 300 // 指定图表高度});chart.source(data);chart.render(); <div id=“c1” className=“charts”></div>配置:ECharts:// 集中在options中进行配置myChart.setOption({ title: { … }, tooltip: {}, xAxis: { data: […] }, yAxis: {}, series: [{ … }]});BizCharts:// 根据组件需要,配置参数之后进行赋值const cols = {…};const data = {…};<Chart width={600} height={400} data={data} scaenter code herele={cols}> …</Chart>G2:chart.tooltip({ triggerOn: ‘…’ showTitle: {boolean}, // 是否展示 title,默认为 true crosshairs: { … style: { … } }});事件:ECharts:事件 api文档myChart.on(‘click’, function (params) { console.log(params);});BizCharts:事件 api文档<chart onEvent={e => { //do something }}/>G2: 事件 api文档chart.on(‘mousedown’, ev => {});总结对比以上3种图表,ECharts和BizCharts相对容易使用,尤其ECharts的配置非常清晰,BizCharts与其也有一定相似之处。BizCharts优势在于组件化的形式使得dom结构相对清晰,按需引用。G2比较适合需要大量图表交互时引用,其丰富的api处理交互逻辑相对更有优势。广而告之本文发布于薄荷前端周刊,欢迎Watch & Star ★,转载请注明出处。欢迎讨论,点个赞再走吧 。◕‿◕。 ~ ...

September 20, 2018 · 3 min · jiezi

聊聊redis的数据结构的应用

序本文主要研究一下redis的数据结构的应用string最常用的就是incr操作,比如可以用来维护用户在某个抽奖活动的剩余抽奖次数setnx方法可以用来实现分布式锁hashmap可以用来存储session,作为分布式session的一个实现方案可以用来存储用户购物车,value值存储的key为物品,value为其数量setset可以用来存储每个标签对应的文章id也可以用来存储每个文章的已投票用户id,通过add返回值可以判断该值之前是否已经存在zsetzset可以用来存储文章的得票数,使用得票数作为score,使用zset排序得出投票最高的前N篇文章或者用来存储最近登录的用户id,使用时间作为score,使用zset排序得出最近登录的前N个用户id也可以存储用户最近浏览的物品,使用时间作为score,使用zset排序得出用户最近浏览的前N个物品也可以存储物品最近浏览的用户,使用时间作为score,使用zset排序得出最近浏览该物品的前N个用户list可以作为简单的消息队列,通过list的lpush以及brpop作为消息队列的入队及消费的操作hyperloglog用来粗略统计网站的每日UVgeo(底层使用zset)使用geo来存储poi信息,比如存储门店的经纬度,之后可以根据半径查询附件的门店信息bitmaps(底层是string结构)用来统计用户每日是否登陆过小结redis之所以比memcache更为流行主要是由于其强大的数据结构及其提供的操作,丰富的数据结构在特定的场景给我们提供了诸多便利,好好合理利用其数据结构特性,是用好redis的前提。docSpringBoot应用之分布式会话redis的GEO实战redis的bitset实战redis的HyperLogLog实战Redis实战SpringBoot版本之购物车服务Redis实战SpringBoot版本之投票服务聊聊jesque在redis中的数据结构

September 20, 2018 · 1 min · jiezi

PHP下的异步尝试二:初识协程

PHP下的异步尝试系列如果你还不太了解PHP下的生成器,你可以根据下面目录翻阅PHP下的异步尝试一:初识生成器PHP下的异步尝试二:初识协程PHP下的异步尝试三:协程的Co自动执行器 [待开发]PHP下的异步尝试四:PHP版的Promise [待开发]…多任务 (并行和并发)在讲协程之前,先谈谈多进程、多线程、并行和并发。对于单核处理器,多进程实现多任务的原理是让操作系统给一个任务每次分配一定的 CPU 时间片,然后中断、让下一个任务执行一定的时间片接着再中断并继续执行下一个,如此反复。由于切换执行任务的速度非常快,给外部用户的感受就是多个任务的执行是同时进行的。多进程的调度是由操作系统来实现的,进程自身不能控制自己何时被调度,也就是说: 进程的调度是由外层调度器抢占式实现的而协程要求当前正在运行的任务自动把控制权回传给调度器,这样就可以继续运行其他任务。这与抢占式的多任务正好相反, 抢占多任务的调度器可以强制中断正在运行的任务, 不管它自己有没有意愿。如果仅依靠程序自动交出控制的话,那么一些恶意程序将会很容易占用全部 CPU 时间而不与其他任务共享。协程的调度是由协程自身主动让出控制权到外层调度器实现的回到刚才生成器实现 xrange 函数的例子,整个执行过程的交替可以用下图来表示:协程可以理解为纯用户态的线程,通过协作而不是抢占来进行任务切换。相对于进程或者线程,协程所有的操作都可以在用户态而非操作系统内核态完成,创建和切换的消耗非常低。简单的说协程 就是提供一种方法来中断当前任务的执行,保存当前的局部变量,下次再过来又可以恢复当前局部变量继续执行。我们可以把大任务拆分成多个小任务轮流执行,如果有某个小任务在等待系统 IO,就跳过它,执行下一个小任务,这样往复调度,实现了 IO 操作和 CPU 计算的并行执行,总体上就提升了任务的执行效率,这也便是协程的意义多线程在单核下,多线程必定是并发的;不过现在的统一进程的多线程是可以运行在多核CPU下,所以可以是并行的并发(Concurrency)是指能处理多个同时性活动的能力,并发事件之间不一定要同一时刻发生。并行(Parallesim)是指同时发生的两个并发事件,具有并发的含义,而并发则不一定并行。多个操作可以在重叠的时间段内进行。并行和并发区别并发指的是程序的结构,并行指的是程序运行时的状态并行一定是并发的,并行是并发设计的一种单线程永远无法达到并行状态协程协程的支持是在生成器的基础上, 增加了可以回送数据给生成器的功能(调用者发送数据给被调用的生成器函数). 这就把生成器到调用者的单向通信转变为两者之间的双向通信.我们在上篇文章已经讲过了send方法, 下面让我们理解下协程同步代码在没有涉及到异步执行代码之前,我们的代码都是这样的function printNum($max, $caller){ for ($i=0; $i<$max; $i++ ) { echo “调度者:” . $caller . " 打印:" . $i . PHP_EOL; }}printNum(3, “caller1”);printNum(3, “caller2”);# output调度者:caller1 打印:0调度者:caller1 打印:1调度者:caller1 打印:2调度者:caller2 打印:0调度者:caller2 打印:1调度者:caller2 打印:2使用协程后改进的代码初稿,手动调整生成器执行# 本代码手动调整了进程执行代码的顺序,当然本代码实现不用协程也可以,只是利用本流程说明协程作用# 生成器给了我们函数中断,协程[生成器send]给了我们重新唤起生成器函数的能力function printNumWithGen($max){ for ($i=0; $i<$max; $i++ ) { $res = yield $i; echo $res; }}$gen1 = printNumWithGen(3);$gen2 = printNumWithGen(3);// 手动执行caller1 再 caller2$gen1->send(“调度者: caller1 打印:” . $gen1->current() . PHP_EOL);$gen2->send(“调度者: caller2 打印:” . $gen2->current() . PHP_EOL);// 手动执行caller1 再 caller2$gen1->send(“调度者: caller1 打印:” . $gen1->current() . PHP_EOL);$gen2->send(“调度者: caller2 打印:” . $gen2->current() . PHP_EOL);// 手动执行caller2 再 caller1$gen2->send(“调度者: caller2 打印:” . $gen2->current() . PHP_EOL);$gen1->send(“调度者: caller1 打印:” . $gen1->current() . PHP_EOL);# output调度者: caller1 打印:0调度者: caller2 打印:0调度者: caller1 打印:1调度者: caller2 打印:1调度者: caller2 打印:2调度者: caller1 打印:2总结上面案例应该让大家理解了协程设计的意义和如何使用协程那么接下去我们为我们的协程自动一个自动调度器(Co自动执行器),无需再手动来中断和恢复了 ...

September 20, 2018 · 1 min · jiezi

做游戏的小伙伴们注意了,DDoS还可以这样破!

欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~本文由腾讯游戏云发表于云+社区专栏作者:腾讯DDoS安全专家、腾讯云游戏安全专家haroldchen 摘要:在游戏出海的过程中,DDoS攻击是游戏企业不得不面对的一个暗礁,如若应对不当,用户流失和口碑受损不可避免。对此,腾讯云新一代高防解决方案团队在中国香港、新加坡、美国硅谷等地上线了多个高防节点,通过一键接入,保证用户服务的稳定可用。此外,腾讯云新一代高防产品推出了创新的游戏行业DDoS防护策略-安全水印方案,用户一旦接入,就能享受到LOL,CF,DNF,王者荣耀等同的防护效果。后续无论攻击者如何尝试,服务器将一如既往地平稳运行,就和什么也没发生一样。7月的深圳,高温已持续一个多月。一家游戏公司的办公区里,平时稳重有加的运维小哥,今天却显得无比焦躁,手指急促地敲击着键盘,额头上沁出细细汗珠。身后的老板眉头紧锁,表情凝重。公司前几天在美服上线的新版本游戏,由于搭配了优化后的架构和高配服务器,以及流畅的体验和新颖的玩法,在海外玩家中获得了良好的口碑。然而今天公司不断接到玩家关于游戏卡顿/掉线的投诉,论坛上也乱成一团,早上还充满了赞誉之声的bbs,此刻却已被玩家的抱怨所淹没。在既没有运营活动,登录玩家数也没有异常的情况下,对局服务器的CPU利用率居然居高不下,网络连接数飙升,多次重启后也没有丝毫改善。这是怎么回事呢?经过从应用,进程,主机等多方面排查,公司最后锁定了数以百万计的IP来源。运维小哥最终确定:我们的游戏服务器遭受了DDoS攻击!DDoS攻击:游戏行业的毒瘤什么是DDoS攻击呢?DDoS攻击,是针对互联网企业的一种网络攻击,堪比游戏行业的毒瘤。和其他网络攻击直接窃取数据、偷盗金钱不同,DDoS攻击会让本该为用户提供服务的资源忙于应付攻击者的请求,从而让服务端瞬间失去服务能力,难以对正常用户的请求进行响应。以游戏企业为例,游戏服务端若不能及时收到/处理客户端发送的数据包,用户这边就会出现画面卡顿、技能释放延迟、玩家沟通不畅等情况,严重的甚至会导致客户端掉线。酣畅淋漓的游戏体验荡然无存,严重影响玩家的体验和留存。所谓武功再高,也怕菜刀。根据腾讯云新一代高防团队的分析,在被DDoS攻击的所有企业中,游戏企业占比高达7成,位居各行业之首。从峰值来看,DDoS攻击的最高峰值可高达1.23T,可谓来势凶猛。可以说,游戏企业已成为最易遭受DDoS攻击的对象,如若应对不当,用户流失和口碑受损不可避免。两派黑客:勒索攻击防不胜防游戏行业经过多年的发展,已成为一个“厮杀惨烈”的红海市场,伴随这个市场蓬勃发展的,还有各路DDoS攻击黑客组织。根据腾讯云新一代高防团队的情报分析,目前发起DDoS攻击的黑客组织,大体上可分为两种流派:DDoS-for-Bitcoin和DDoS-for-Hire 。DDoS-for-Hire较为典型。顾名思义,黑客受雇于行业恶意竞争者,替其向竞争对手发起DDoS攻击,以此获得丰厚的佣金。而因为DDoS攻击,竞品游戏体验下降,用户流失,恶性竞争者最终坐收渔翁之利。这个行业发展至今,甚至出现了部分站点通过提供技术支持,为黑客发起DDoS攻击大开方便之门,以从中收取数十至数百美元不等的会员费用,或者单次攻击数美元至数百美元的服务费,赚得盆满钵满。图片来源:国外DDoS攻击站点rocketstress.com而DDoS-for-Bitcoin 则更像是cyber世界的勒索者,以Armada Collective团伙最为典型。黑客一般通过邮件联系到企业负责人,勒索巨额的赎金或者比特币。一旦勒索请求被拒绝,企业将遭受数百G的DDoS攻击——这对于绝大部分中小互联网企业来说,无疑是灭顶之灾。今年以来,一个名为ACCN攻击小组的团伙(号称大多数成员来自Armada Collective),就对腾讯云上的多家游戏企业进行勒索并发起DDoS攻击。然而,交与不交,都是个问题。被勒索企业即使交了赎金,后续也无法避免不被继续勒索,或其他团伙的勒索。全球难题: DDoS攻击凶猛异常对于立志要出海的游戏企业来说,海外游戏市场虽然广阔,但是面临的DDoS攻击团伙数量更多、组织更为缜密,DDoS攻击的态势也更为猛烈。2016年9月,在线DDoS攻击服务商"vDOS"被取缔,其关闭前已成功帮助客户发起了15万次以上的DDoS攻击。今年4月,全球最大的DDoS市场WebStresser被关闭,关闭前WebStresser拥有超过13.6万名注册用户,近年来已提供超过400万次DDoS攻击服务!这仅仅只是海外DDoS攻击的冰山一角。而在这凶猛的DDoS攻击下,即使全球收入排名前列的游戏企业都深受其害。2017年8月,PoodleCorp的黑客组织对包括《守望先锋》《风暴英雄》《魔兽世界》在内的多个爆款游戏发起攻击,导致游戏网络出现异常,技术支持服务一度中断。今年7月,另一款爆款游戏《For Honor》遭受DDoS攻击,导致玩家无法连接到服务器,游戏厂商最后不得不通过发放游戏道具对玩家进行补偿,损失惨重。上述爆款游戏全都出自全球排名靠前的游戏企业,背后技术力量雄厚,尚且不能避免DDoS攻击,可以预见,对于试图出海拓展业务的中小游戏企业而言,如若未做好充分的准备,将很难在如此惨烈的DDoS攻击中绝地求生。出海必备:腾讯云新一代高防解决方案早在2017年,腾讯云新一代高防团队就注意到,越来越多的游戏行业用户,依靠腾讯云提供的便捷基础设施,努力开拓海外市场。在和大量客户沟通后我们了解到,在游戏出海的过程中,除了要克服文化和监管政策的差异之外,DDoS攻击也是游戏企业不得不面对的一个暗礁。基于此,针对海外DDoS攻击频发的特点,腾讯云新一代高防团队在中国香港、新加坡、美国硅谷等地上线了多个高防节点,通过一键接入,保证用户在享受本地接入的便利的同时,也确保了服务的稳定可用。此外,腾讯云新一代高防团队和行业内合作伙伴一起建设超大防护容量的高防节点,也将于近日在硅谷上线,可以让用户从容应对当前最大规模的DDoS攻击。针对游戏行业大量采用私有协议,部分攻击流量和业务流量难以区分的特点,腾讯云新一代高防团队提炼出了游戏行业通用的DDoS攻击防护解决方案——安全水印方案,该技术解决方案依托腾讯自营游戏10年DDoS对抗经验,安全性和可靠性经过了充分的实战检验;用户一旦接入,就能享受到LOL,CF,DNF,王者荣耀等同的防护效果。此外,腾讯云新一代高防解决方案目前支持IPv4和IPv6透明部署,可以完美应对美国IPv6部署比例超过30%的情况,双栈防护,为客户业务从IPv4过渡到IPv6提供平滑支持。回到开头的故事,运维小哥在开通腾讯云高防服务,并得到腾讯云新一代高防团队的专家支持后,服务器迅速恢复正常。之后在腾讯云新一代高防团队的专家建议下,游戏也接入了安全水印方案。后续无论攻击者如何尝试,服务器将一如既往地平稳运行,就和什么也没发生一样。了解更多腾讯云新一代高防产品相关信息,请戳:https://cloud.tencent.com/pro…问答游戏体系结构相关阅读团战开黑必备“良药”了解一下!再也不用担心网吧开黑队友听不清了!3行代码,为QQ轻游戏加上语音互动能力 【每日课程推荐】机器学习实战!快速入门在线广告业务及CTR相应知识

September 20, 2018 · 1 min · jiezi

MySQL float猜想

问题描述使用JPA映射一个float类型到数据库:然后存储129364.57,发现存储的结果是129365。从上周就开始研究这个问题,查阅了各种资料,网上许多人都说是因为MySQL默认是保留六位有效数字,自己测试了一下也确实是这样。但是查询MYSQL官方文档,并没有找到依据。MySQL官方文档:Float - MySQL如果你看到了这篇文章,欢迎评论发表意见,让我们互相学习、进步。结论写的非常好的一篇文章,MySQL存储的各种尝试:MySQL数字类型int与tinyint、float与decimal如何选择用JPA映射的Float默认的长度与小数点都是0。这是测试的结果:float默认能精确到6位有效数字!原理猜想这是MySQL官方文档对float和double的描述:官方文档并没有说MySQL是如何存储浮点数的,所以如果没有去读过其源代码,所有的博客都只是猜想。IEEE 754目前大多数人认为MySQL内部是采用IEEE 754进行存储的,IEEE 754 - 维基百科。IEEE二进制浮点数算术标准(IEEE 754)是20世纪80年代以来最广泛使用的浮点数运算标准,为许多CPU与浮点运算器所采用。以32位的单精度浮点数为例:与日常所说的科学计数法类似。因为底数是有效数字,所以第一位肯定是1,所以这个1不进行存储,所以虽然是23位的底数,但是实际的底数位数其实是24位,含有一个隐含的1。双精度与此类似,一个符号位,指数位为11,尾数为52位,合计64位。假设假设是用MySQL是用IEEE 754标准存储的浮点数。用单精度存储129364.57,其二进制为11111100101010100.1001000111101011100001010001111011。正数:符号位为0。指数位为:00010000(十进制中的16)。去掉第一个1,保留23位,底数为:11111001010101001001000。所以最后的结果是11111100101010100.1001000,转换为十进制为:129364.5625。但是实际的MySQL存储后的结果为129365,所以猜想要么MySQL就是不是按IEEE 754存储的,要么就是按这个存储的但是内部为了数据库的性能等或其他的优化对数据进行了处理。这里我这里更倾向于第二种,毕竟IEEE 754是一种国际标准,没有理由不遵守。

September 20, 2018 · 1 min · jiezi

Web通信协议,你还需要知道: SPDY 和 QUIC

上一篇文章前端阶段性总结(一): HTTP 协议,从1.0到2.0梳理了一下HTTP协议。本文将重点介绍,在前端通信协议中HTTP2的前身,具有开拓性意义的SPDY,以及具有颠覆性的QUIC协议。一、开拓者:SPDY1. 简介:spdy 是由google推行的,改进版本的HTTP1.1 (那时候还没有HTTP2)。它基于TCP协议,在HTTP的基础上,结合HTTP1.X的多个痛点进行改进和升级的产物。它的出现使web的加载速度有极大的提高。HTTP2也借鉴了很多spdy的特性。2. 特性:上一篇文章中有介绍,基本和HTTP2差不多,这里就不赘述了:多路复用头部压缩服务器推送请求优先级spdy的架构图:3. 现状:在HTTP2未推出之前,spdy在业界内已经有一定的市场占用量,并且它的成绩也是不容忽视的,因此在很长一段时间,市场上可能会见到spdy和h2同时使用的场景。二、颠覆者:QUIC1. 前置知识:TCP 与 UDPTCP(Transmission Control Protocol 传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。1)提供IP环境下的数据可靠传输(一台计算机发出的字节流会无差错的发往网络上的其他计算机,而且计算机A接收数据包的时候,也会向计算机B回发数据包,这也会产生部分通信量),有效流控,全双工操作(数据在两个方向上能同时传递),多路复用服务,是面向连接,端到端的传输;2)面向连接:正式通信前必须要与对方建立连接。事先为所发送的数据开辟出连接好的通道,然后再进行数据发送,像打电话。3)TCP支持的应用协议:Telnet(远程登录)、FTP(文件传输协议)、SMTP(简单邮件传输协议)。TCP用于传输数据量大,可靠性要求高的应用。UDP(User Datagram Protocol用户数据报协议)是OSI(Open System Interconnection,开放式系统互联) 参考模型中一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务。1) 面向非连接的(正式通信前不必与对方建立连接,不管对方状态就直接发送,像短信,QQ),不能提供可靠性、流控、差错恢复功能。UDP用于一次只传送少量数据,可靠性要求低、传输经济等应用。2) UDP支持的应用协议:NFS(网络文件系统)、SNMP(简单网络管理系统)、DNS(主域名称系统)、TFTP(通用文件传输协议)等。总的来说:TCP:面向连接、传输可靠(保证数据正确性,保证数据顺序)、用于传输大量数据(流模式)、速度慢,建立连接需要开销较多(时间,系统资源)。UDP:面向非连接、传输不可靠、用于传输少量数据(数据包模式)、速度快。Diffie-Hellman 算法D-H算法的数学基础是离散对数的数学难题,其加密过程如下:(1)客户端与服务端确定两个大素数 n和 g,这两个数不用保密(2)客户端选择另一个大随机数x,并计算A如下:A = g^x mod n(3)客户端将 A 发给服务端(4)服务端选择另一个大随机数y,并计算B如下:B = g^y mod n(5)服务端将B发给客户端(7)计算秘密密钥K1如下:K1=B^2 mod n , 计算秘密密钥K2如下:K2=A^y mod n , K1=K2,因此服务端和客户端可以用其进行加解密攻击者知道n和g,并且截获了A和B,但是当它们都是非常大的数的时候,依靠这四个数来计算k1和k2非常困难,这就是离散对数数学难题。2. 什么是QUIC?quic 是google推出的,基于UDP的高效可靠协议。quic在UDP的基础上实现了TCP的一些特性,而由于底层使用的是UDP,所以传输效率比TCP高效。3. 特性a. 基于UDP建立的连接:我们知道,基于TCP的协议,如http2,在首次建立连接的时候需要进行三次握手,即至少需要3个ntt,而考虑安全HTTPS的TLS层,又需要至少次的通信才能协商出密钥。这在短连接的场景中极大的增加了网络延迟,而这种延迟是无法避免的。而基于UDP的quic协议,则不需要3次握手的过程,甚至在安全协商阶段只需要进行12次的协商通信,即可建立安全稳定的连接,极大的减少了网络延迟。b. 基于Diffie-Hellman的加密算法:HTTPS 使用的是 TLS + SSL 的加密手段,在交换证书、协商密钥的过程中,至少需要2次ntt进行协商通信。而quic使用了Diffie-Hellman算法,算法的原理使得客户端和浏览器之间只需要1次的协商就能获得通信密钥,quic建立安全链接的详细过程:客户端发起Inchoate client hello服务器返回Rejection,包括密钥交换算法的公钥信息,算法信息,证书信息等被放到server config中传给客户端客户端发起client hello,包括客户端公钥信息后续发起连接的过程中,一旦客户端缓存或持久化了server config,就可以复用并结合本地生成的私钥进行加密数据传输了,不需要再次握手,从而实现0RTT建立连接。c. 改进的多路复用我们知道,无论是HTTP2还是SPDY,基于TCP的协议尽管实现了多路复用,但仍然没有避免队头阻塞的问题,这个问题是由于TCP底层的实现造成的,即TCP的包有严格的顺序控制,前序包如果丢失,则后续包即使返回了也不会通知到应用层协议,从而导致了前序包阻塞。而quic基于UDP则天然的避免了这个问题,由于UDP没有严格的包顺序,一个包的阻塞只会影响它自身,并不会影响到其他资源,且quic也实现了类似HTTP2的多路复用,这种没有队头阻塞的多路复用对延迟的降低是显而易见的。d. 连接的迁移在以往的基于TCP的协议中,往往使用四元组(源IP,源端口,目的IP,目的端口)来标识一条连接,当四元组中的IP或端口任一个发生变化了连接就需要重新建立,从而不具备连接迁移的能力。而QUIC使用了connection id对连接进行唯一标识。即使网络从4G变成了wifi,只要两次连接中的connection id不变,并且客户端或者服务器能通过校验,就不需要重新建立连接,连接迁移就能成功。这在移动端场景的优势极为明显,因为手机经常会在wifi和4g中切换,使用quic协议降低了重建连接的成本。e. 协商的升级在chorme浏览器中,发起一个TCP请求,这个请求会同时与服务器开始建立tcp 和 quic 的连接(前提是服务器支持),如果quic连接先建立成功,则使用quic建立的连接通信,反之,则使用tcp建立的连接进行通信。具体步骤如下:1、客户端发出tcp请求2、服务端如果支持quic可以通过响应头alt-svc告知客户端3、客户端同时发起tcp连接和quic连接竞赛4、一旦quic建立连接获胜则采用quic协议发送请求5、如遇网络或服务器不支持quic/udp,客户端标记quic为broken6、传输中的请求通过tcp重发7、5min后尝试重试quic,下一次尝试增大到10min8、一旦再次成功采用quic并把broken标记取消其中,支持quic的alt-svc头部信息如下图示:d. 其他特性:改进的拥塞控制丢包恢复底层的连接持久化head stream 保证包顺序双级别流量控制三、总结与思考在web通信协议的演进中,SPDY是不可或缺的角色,在没有HTTP2的时代,它的出现极大的提升了网页加载速度,甚至HTTP2也是吸取了它很多的特性而制定的,是当之无愧的开拓者。而在有HTTP2的今天,quic协议的出现无异于对TCP的颠覆,它在底层抛弃了传统的TCP,而使用高效的UDP,并实现了TCP的特性,并且没有修改到应用层协议,我们可以无缝的基于原有的规范去开发。最后,这两东西居然都是google提出并推行的。只能说google真的牛叉参考文章https://www.jianshu.com/p/2d8…https://wetest.qq.com/lab/vie...https://cloud.tencent.com/dev...https://www.zhihu.com/questio…

September 5, 2018 · 1 min · jiezi

有赞搜索系统的架构演进

有赞搜索平台是一个面向公司内部各项搜索应用以及部分 NoSQL 存储应用的 PaaS 产品,帮助应用合理高效的支持检索和多维过滤功能,有赞搜索平台目前支持了大大小小一百多个检索业务,服务于近百亿数据。在为传统的搜索应用提供高级检索和大数据交互能力的同时,有赞搜索平台还需要为其他比如商品管理、订单检索、粉丝筛选等海量数据过滤提供支持,从工程的角度看,如何扩展平台以支持多样的检索需求是一个巨大的挑战。我是有赞搜索团队的第一位员工,也有幸负责设计开发了有赞搜索平台到目前为止的大部分功能特性,我们搜索团队目前主要负责平台的性能、可扩展性和可靠性方面的问题,并尽可能降低平台的运维成本以及业务的开发成本。ElasticsearchElasticsearch 是一个高可用分布式搜索引擎,一方面技术相对成熟稳定,另一方面社区也比较活跃,因此我们在搭建搜索系统过程中也是选择了 Elasticsearch 作为我们的基础引擎。架构1.0时间回到 2015 年,彼时运行在生产环境的有赞搜索系统是一个由几台高配虚拟机组成的 Elasticsearch 集群,主要运行商品和粉丝索引,数据通过 Canal 从 DB 同步到 Elasticsearch,大致架构如下:通过这种方式,在业务量较小时,可以低成本的快速为不同业务索引创建同步应用,适合业务快速发展时期,但相对的每个同步程序都是单体应用,不仅与业务库地址耦合,需要适应业务库快速的变化,如迁库、分库分表等,而且多个 canal 同时订阅同一个库,也会造成数据库性能的下降。另外 Elasticsearch 集群也没有做物理隔离,有一次促销活动就因为粉丝数据量过于庞大导致 Elasticsearch 进程 heap 内存耗尽而 OOM,使得集群内全部索引都无法正常工作,这给我上了深深的一课。架构 2.0我们在解决以上问题的过程中,也自然的沉淀出了有赞搜索的 2.0 版架构,大致架构如下:首先数据总线将数据变更消息同步到 mq,同步应用通过消费 mq 消息来同步业务库数据,借数据总线实现与业务库的解耦,引入数据总线也可以避免多个 canal 监听消费同一张表 binlog 的虚耗。高级搜索(Advanced Search)随着业务发展,我们也逐渐出现了一些比较中心化的流量入口,比如分销、精选等,这时普通的 bool 查询并不能满足我们对搜索结果的细粒率排序控制需求,将复杂的 function_score 之类专业性较强的高级查询编写和优化工作交给业务开发负责显然是个不可取的选项,这里我们考虑的是通过一个高级查询中间件拦截业务查询请求,在解析出必要的条件后重新组装为高级查询交给引擎执行,大致架构如下:这里另外做的一点优化是加入了搜索结果缓存,常规的文本检索查询 match 每次执行都需要实时计算,在实际的应用场景中这并不是必须的,用户在一定时间段内(比如 15 或 30 分钟)通过同样的请求访问到同样的搜索结果是完全可以接受的,在中间件做一次结果缓存可以避免重复查询反复执行的虚耗,同时提升中间件响应速度,对高级搜索比较感兴趣的同学可以阅读另外一篇文章《有赞搜索引擎实践(工程篇)》,这里不再细述。大数据集成搜索应用和大数据密不可分,除了通过日志分析来挖掘用户行为的更多价值之外,离线计算排序综合得分也是优化搜索应用体验不可缺少的一环,在 2.0 阶段我们通过开源的 es-hadoop 组件搭建 hive 与 Elasticsearch 之间的交互通道,大致架构如下:通过 flume 收集搜索日志存储到 hdfs 供后续分析,也可以在通过 hive 分析后导出作为搜索提示词,当然大数据为搜索业务提供的远不止于此,这里只是简单列举了几项功能。问题这样的架构支撑了搜索系统一年多的运行,但是也暴露出了许多问题,首当其冲的是越发高昂的维护成本,除去 Elasticsearch 集群维护和索引本身的配置、字段变更,虽然已经通过数据总线与业务库解耦,但是耦合在同步程序中的业务代码依旧为团队带来了极大的维护负担。消息队列虽然一定程序上减轻了我们与业务的耦合,但是带来的消息顺序问题也让不熟悉业务数据状态的我们很难处理。这些问题我总结在之前写过的一篇文章。除此之外,流经 Elasticsearch 集群的业务流量对我们来说呈半黑盒状态,可以感知,但不可预测,也因此出现过线上集群被内部大流量错误调用压到CPU占满不可服务的故障。目前的架构 3.0针对 2.0 时代的问题,我们在 3.0 架构中做了一些针对性调整,列举主要的几点:通过开放接口接收用户调用,与业务代码完全解耦;增加 proxy 用来对外服务,预处理用户请求并执行必要的流控、缓存等操作;提供管理平台简化索引变更和集群管理这样的演变让有赞搜索系统逐渐的平台化,已经初具了一个搜索平台的架构:Proxy作为对外服务的出入口,proxy 除了通过 ESLoader 提供兼容不同版本 Elasticsearch 调用的标准化接口之外,也内嵌了请求校验、缓存、模板查询等功能模块。请求校验主要是对用户的写入、查询请求进行预处理,如果发现字段不符、类型错误、查询语法错误、疑似慢查询等操作后以 fast fail 的方式拒绝请求或者以较低的流控水平执行,避免无效或低效能操作对整个 Elasticsearch 集群的影响。缓存和 ESLoader 主要是将原先高级搜索中的通用功能集成进来,使得高级搜索可以专注于搜索自身的查询分析和重写排序功能,更加内聚。我们在缓存上做了一点小小的优化,由于查询结果缓存通常来说带有源文档内容会比较大,为了避免流量高峰频繁访问导致 codis 集群网络拥堵,我们在 proxy 上实现了一个简单的本地缓存,在流量高峰时自动降级。这里提一下模板查询,在查询结构(DSL)相对固定又比较冗长的情况下,比如商品类目筛选、订单筛选等,可以通过模板查询(search template)来实现,一方面简化业务编排DSL的负担,另一方面还可以通过编辑查询模板 template,利用默认值、可选条件等手段在服务端进行线上查询性能调优。管理平台为了降低日常的索引增删、字段修改、配置同步上的维护成本,我们基于 Django 实现了最初版本的搜索管理平台,主要提供一套索引变更的审批流以及向不同集群同步索引配置的功能,以可视化的方式实现索引元数据的管理,减少我们在平台日常维护上的时间成本。由于开源 head 插件在效果展示上的不友好,以及暴露了部分粗暴功能:如图,可以通过点按字段使得索引按指定字段排序展示结果,在早期版本 Elasticsearch 会通过 fielddata 加载需要排序的字段内容,如果字段数据量比较大,很容易导致 heap 内存占满引发 full gc 甚至 OOM,为了避免重复出现此类问题,我们也提供了定制的可视化查询组件以支持用户浏览数据的需求。ESWriter由于 es-hadoop 仅能通过控制 map-reduce 个数来调整读写流量,实际上 es-hadoop 是以 Elasticsearch 是否拒绝请求来调整自身行为,对线上工作的集群相当不友好。为了解决这种离线读写流量上的不可控,我们在现有的 DataX 基础上开发了一个 ESWriter 插件,能够实现记录条数或者流量大小的秒级控制。挑战平台化以及配套的文档体系完善降低了用户的接入门槛,随着业务的快速增长,Elasticsearch 集群本身的运维成本也让我们逐渐不堪,虽然有物理隔离的多个集群,但不可避免的会有多个业务索引共享同一个物理集群,在不同业务间各有出入的生产标准上支持不佳,在同一个集群内部署过多的索引也是生产环境稳定运行的一个隐患。另外集群服务能力的弹性伸缩相对困难,水平扩容一个节点都需要经历机器申请、环境初始化、软件安装等步骤,如果是物理机还需要更长时间的机器采购过程,不能及时响应服务能力的不足。未来的架构 4.0当前架构通过开放接口接受用户的数据同步需求,虽然实现了与业务解耦,降低了我们团队自身的开发成本,但是相对的用户开发成本也变高了,数据从数据库到索引需要经历从数据总线获取数据、同步应用处理数据、调用搜索平台开放接口写入数据三个步骤,其中从数据总线获取数据与写入搜索平台这两个步骤在多个业务的同步程序中都会被重复开发,造成资源浪费。这里我们目前也准备与 PaaS 团队内自研的DTS(Data Transporter,数据同步服务)进行集成,通过配置化的方式实现 Elasticsearch 与多种数据源之间的自动化数据同步。要解决共享集群应对不同生产标准应用的问题,我们希望进一步将平台化的搜索服务提升为云化的服务申请机制,配合对业务的等级划分,将核心应用独立部署为相互隔离的物理集群,而非核心应用通过不同的应用模板申请基于 k8s 运行的 Elasticsearch 云服务。应用模板中会定义不同应用场景下的服务配置,从而解决不同应用的生产标准差异问题,而且云服务可以根据应用运行状况及时进行服务的伸缩容。小结本文从架构上介绍了有赞搜索系统演进产生的背景以及希望解决的问题,涉及具体技术细节的内容我们将会在本系列的下一篇文章中更新。 ...

September 5, 2018 · 1 min · jiezi

细数那些不懂Spring底层原理带来的伤与痛

什么是spring?Spring 是个Java企业级应用的开源开发框架。Spring主要用来开发Java应用,但是有些扩展是针对构建J2EE平台的web应用。Spring 框架目标是简化Java企业级应用开发,并通过POJO为基础的编程模型促进良好的编程习惯。2. 使用Spring框架的好处是什么?轻量:Spring 是轻量的,基本的版本大约2MB。控制反转:Spring通过控制反转实现了松散耦合,对象们给出它们的依赖,而不是创建或查找依赖的对象们。面向切面的编程(AOP):Spring支持面向切面的编程,并且把应用业务逻辑和系统服务分开。容器:Spring 包含并管理应用中对象的生命周期和配置。MVC框架:Spring的WEB框架是个精心设计的框架,是Web框架的一个很好的替代品。事务管理:Spring 提供一个持续的事务管理接口,可以扩展到上至本地事务下至全局事务(JTA)。异常处理:Spring 提供方便的API把具体技术相关的异常(比如由JDBC,Hibernate or JDO抛出的)转化为一致的unchecked 异常。3. Spring由哪些模块组成?以下是Spring 框架的基本模块:Core moduleBean moduleContext moduleExpression Language moduleJDBC moduleORM moduleOXM moduleJava Messaging Service(JMS) moduleTransaction moduleWeb moduleWeb-Servlet moduleWeb-Struts moduleWeb-Portlet module4. 核心容器(应用上下文) 模块。这是基本的Spring模块,提供spring 框架的基础功能,BeanFactory 是 任何以spring为基础的应用的核心。Spring 框架建立在此模块之上,它使Spring成为一个容器。5. BeanFactory – BeanFactory 实现举例。Bean 工厂是工厂模式的一个实现,提供了控制反转功能,用来把应用的配置和依赖从正真的应用代码中分离。最常用的BeanFactory 实现是XmlBeanFactory 类。6. XMLBeanFactory最常用的就是org.springframework.beans.factory.xml.XmlBeanFactory ,它根据XML文件中的定义加载beans。该容器从XML 文件读取配置元数据并用它去创建一个完全配置的系统或应用。7. 解释AOP模块AOP模块用于发给我们的Spring应用做面向切面的开发, 很多支持由AOP联盟提供,这样就确保了Spring和其他AOP框架的共通性。这个模块将元数据编程引入Spring。8. 解释JDBC抽象和DAO模块。通过使用JDBC抽象和DAO模块,保证数据库代码的简洁,并能避免数据库资源错误关闭导致的问题,它在各种不同的数据库的错误信息之上,提供了一个统一的异常访问层。它还利用Spring的AOP 模块给Spring应用中的对象提供事务管理服务。9. 解释对象/关系映射集成模块。Spring 通过提供ORM模块,支持我们在直接JDBC之上使用一个对象/关系映射映射(ORM)工具,Spring 支持集成主流的ORM框架,如Hiberate,JDO和 iBATIS SQL Maps。Spring的事务管理同样支持以上所有ORM框架及JDBC。10. 解释WEB 模块。Spring的WEB模块是构建在application context 模块基础之上,提供一个适合web应用的上下文。这个模块也包括支持多种面向web的任务,如透明地处理多个文件上传请求和程序级请求参数的绑定到你的业务对象。它也有对Jakarta Struts的支持。11. Spring配置文件Spring配置文件是个XML 文件,这个文件包含了类信息,描述了如何配置它们,以及如何相互调用。12. 什么是Spring IOC 容器?Spring IOC 负责创建对象,管理对象(通过依赖注入(DI),装配对象,配置对象,并且管理这些对象的整个生命周期。13. IOC的优点是什么?IOC 或 依赖注入把应用的代码量降到最低。它使应用容易测试,单元测试不再需要单例和JNDI查找机制。最小的代价和最小的侵入性使松散耦合得以实现。IOC容器支持加载服务时的饿汉式初始化和懒加载。15. ApplicationContext通常的实现是什么?FileSystemXmlApplicationContext :此容器从一个XML文件中加载beans的定义,XML Bean 配置文件的全路径名必须提供给它的构造函数。ClassPathXmlApplicationContext:此容器也从一个XML文件中加载beans的定义,这里,你需要正确设置classpath因为这个容器将在classpath里找bean配置。WebXmlApplicationContext:此容器加载一个XML文件,此文件定义了一个WEB应用的所有bean。16. Bean 工厂和 Application contexts 有什么区别?Application contexts提供一种方法处理文本消息,一个通常的做法是加载文件资源(比如镜像),它们可以向注册为监听器的bean发布事件。另外,在容器或容器内的对象上执行的那些不得不由bean工厂以程序化方式处理的操作,可以在Application contexts中以声明的方式处理。Application contexts实现了MessageSource接口,该接口的实现以可插拔的方式提供获取本地化消息的方法。17. 一个Spring的应用看起来象什么?一个定义了一些功能的接口。这实现包括属性,它的Setter , getter 方法和函数等。Spring AOP。Spring 的XML 配置文件。使用以上功能的客户端程序。依赖注入18. 什么是Spring的依赖注入?依赖注入,是IOC的一个方面,是个通常的概念,它有多种解释。这概念是说你不用创建对象,而只需要描述它如何被创建。你不在代码里直接组装你的组件和服务,但是要在配置文件里描述哪些组件需要哪些服务,之后一个容器(IOC容器)负责把他们组装起来。19. 有哪些不同类型的IOC(依赖注入)方式?构造器依赖注入:构造器依赖注入通过容器触发一个类的构造器来实现的,该类有一系列参数,每个参数代表一个对其他类的依赖。Setter方法注入:Setter方法注入是容器通过调用无参构造器或无参static工厂 方法实例化bean之后,调用该bean的setter方法,即实现了基于setter的依赖注入。20. 哪种依赖注入方式你建议使用,构造器注入,还是 Setter方法注入?你两种依赖方式都可以使用,构造器注入和Setter方法注入。最好的解决方案是用构造器参数实现强制依赖,setter方法实现可选依赖。Spring Beans21.什么是Spring beans?Spring beans 是那些形成Spring应用的主干的java对象。它们被Spring IOC容器初始化,装配,和管理。这些beans通过容器中配置的元数据创建。比如,以XML文件中<bean/> 的形式定义。Spring 框架定义的beans都是单件beans。在bean tag中有个属性”singleton”,如果它被赋为TRUE,bean 就是单件,否则就是一个 prototype bean。默认是TRUE,所以所有在Spring框架中的beans 缺省都是单件。22. 一个 Spring Bean 定义 包含什么?一个Spring Bean 的定义包含容器必知的所有配置元数据,包括如何创建一个bean,它的生命周期详情及它的依赖。23. 如何给Spring 容器提供配置元数据?这里有三种重要的方法给Spring 容器提供配置元数据。XML配置文件。基于注解的配置。基于java的配置。24. 你怎样定义类的作用域?当定义一个<bean> 在Spring里,我们还能给这个bean声明一个作用域。它可以通过bean 定义中的scope属性来定义。如,当Spring要在需要的时候每次生产一个新的bean实例,bean的scope属性被指定为prototype。另一方面,一个bean每次使用的时候必须返回同一个实例,这个bean的scope 属性 必须设为 singleton。25. 解释Spring支持的几种bean的作用域。Spring框架支持以下五种bean的作用域:singleton : bean在每个Spring ioc 容器中只有一个实例。prototype:一个bean的定义可以有多个实例。request:每次http请求都会创建一个bean,该作用域仅在基于web的Spring ApplicationContext情形下有效。session:在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。global-session:在一个全局的HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。缺省的Spring bean 的作用域是Singleton.26. Spring框架中的单例bean是线程安全的吗?不,Spring框架中的单例bean不是线程安全的。27. 解释Spring框架中bean的生命周期。Spring容器 从XML 文件中读取bean的定义,并实例化bean。Spring根据bean的定义填充所有的属性。如果bean实现了BeanNameAware 接口,Spring 传递bean 的ID 到 setBeanName方法。如果Bean 实现了 BeanFactoryAware 接口, Spring传递beanfactory 给setBeanFactory 方法。如果有任何与bean相关联的BeanPostProcessors,Spring会在postProcesserBeforeInitialization()方法内调用它们。如果bean实现IntializingBean了,调用它的afterPropertySet方法,如果bean声明了初始化方法,调用此初始化方法。如果有BeanPostProcessors 和bean 关联,这些bean的postProcessAfterInitialization() 方法将被调用。如果bean实现了 DisposableBean,它将调用destroy()方法。28. 哪些是重要的bean生命周期方法? 你能重载它们吗?有两个重要的bean 生命周期方法,第一个是setup , 它是在容器加载bean的时候被调用。第二个方法是 teardown 它是在容器卸载类的时候被调用。The bean 标签有两个重要的属性(init-method和destroy-method)。用它们你可以自己定制初始化和注销方法。它们也有相应的注解(@PostConstruct和@PreDestroy)。29. 什么是Spring的内部bean?当一个bean仅被用作另一个bean的属性时,它能被声明为一个内部bean,为了定义inner bean,在Spring 的 基于XML的 配置元数据中,可以在 <property/>或 <constructor-arg/> 元素内使用<bean/> 元素,内部bean通常是匿名的,它们的Scope一般是prototype。30. 在 Spring中如何注入一个java集合?Spring提供以下几种集合的配置元素:<list>类型用于注入一列值,允许有相同的值。<set> 类型用于注入一组值,不允许有相同的值。<map> 类型用于注入一组键值对,键和值都可以为任意类型。<props>类型用于注入一组键值对,键和值都只能为String类型。31. 什么是bean装配?装配,或bean 装配是指在Spring 容器中把bean组装到一起,前提是容器需要知道bean的依赖关系,如何通过依赖注入来把它们装配到一起。32. 什么是bean的自动装配?Spring 容器能够自动装配相互合作的bean,这意味着容器不需要<constructor-arg>和<property>配置,能通过Bean工厂自动处理bean之间的协作。33. 解释不同方式的自动装配 。有五种自动装配的方式,可以用来指导Spring容器用自动装配方式来进行依赖注入。no:默认的方式是不进行自动装配,通过显式设置ref 属性来进行装配。byName:通过参数名 自动装配,Spring容器在配置文件中发现bean的autowire属性被设置成byname,之后容器试图匹配、装配和该bean的属性具有相同名字的bean。byType::通过参数类型自动装配,Spring容器在配置文件中发现bean的autowire属性被设置成byType,之后容器试图匹配、装配和该bean的属性具有相同类型的bean。如果有多个bean符合条件,则抛出错误。constructor:这个方式类似于byType, 但是要提供给构造器参数,如果没有确定的带参数的构造器参数类型,将会抛出异常。autodetect:首先尝试使用constructor来自动装配,如果无法工作,则使用byType方式。34.自动装配有哪些局限性 ?自动装配的局限性是:重写: 你仍需用 <constructor-arg>和 <property> 配置来定义依赖,意味着总要重写自动装配。基本数据类型:你不能自动装配简单的属性,如基本数据类型,String字符串,和类。模糊特性:自动装配不如显式装配精确,如果有可能,建议使用显式装配。35. 你可以在Spring中注入一个null 和一个空字符串吗?可以。Spring注解36. 什么是基于Java的Spring注解配置? 给一些注解的例子.基于Java的配置,允许你在少量的Java注解的帮助下,进行你的大部分Spring配置而非通过XML文件。以@Configuration 注解为例,它用来标记类可以当做一个bean的定义,被Spring IOC容器使用。另一个例子是@Bean注解,它表示此方法将要返回一个对象,作为一个bean注册进Spring应用上下文。37. 什么是基于注解的容器配置?相对于XML文件,注解型的配置依赖于通过字节码元数据装配组件,而非尖括号的声明。开发者通过在相应的类,方法或属性上使用注解的方式,直接组件类中进行配置,而不是使用xml表述bean的装配关系。38. 怎样开启注解装配?注解装配在默认情况下是不开启的,为了使用注解装配,我们必须在Spring配置文件中配置 <context:annotation-config/>元素。39. @Required 注解这个注解表明bean的属性必须在配置的时候设置,通过一个bean定义的显式的属性值或通过自动装配,若@Required注解的bean属性未被设置,容器将抛出BeanInitializationException。40. @Autowired 注解@Autowired 注解提供了更细粒度的控制,包括在何处以及如何完成自动装配。它的用法和@Required一样,修饰setter方法、构造器、属性或者具有任意名称和/或多个参数的PN方法。41. @Qualifier 注解当有多个相同类型的bean却只有一个需要自动装配时,将@Qualifier 注解和@Autowire 注解结合使用以消除这种混淆,指定需要装配的确切的bean。Spring数据访问42.在Spring框架中如何更有效地使用JDBC?使用SpringJDBC 框架,资源管理和错误处理的代价都会被减轻。所以开发者只需写statements 和 queries从数据存取数据,JDBC也可以在Spring框架提供的模板类的帮助下更有效地被使用,这个模板叫JdbcTemplate (例子见这里here)43. JdbcTemplateJdbcTemplate 类提供了很多便利的方法解决诸如把数据库数据转变成基本数据类型或对象,执行写好的或可调用的数据库操作语句,提供自定义的数据错误处理。44. Spring对DAO的支持Spring对数据访问对象(DAO)的支持旨在简化它和数据访问技术如JDBC,Hibernate or JDO 结合使用。这使我们可以方便切换持久层。编码时也不用担心会捕获每种技术特有的异常。45. 使用Spring通过什么方式访问Hibernate?在Spring中有两种方式访问Hibernate:控制反转 Hibernate Template和 Callback。继承 HibernateDAOSupport提供一个AOP 拦截器。46. Spring支持的ORMSpring支持以下ORM:HibernateiBatisJPA (Java Persistence API)TopLinkJDO (Java Data Objects)47.如何通过HibernateDaoSupport将Spring和Hibernate结合起来?用Spring的 SessionFactory 调用 LocalSessionFactory。集成过程分三步:配置the Hibernate SessionFactory。继承HibernateDaoSupport实现一个DAO。在AOP支持的事务中装配。48. Spring支持的事务管理类型Spring支持两种类型的事务管理:编程式事务管理:这意味你通过编程的方式管理事务,给你带来极大的灵活性,但是难维护。声明式事务管理:这意味着你可以将业务代码和事务管理分离,你只需用注解和XML配置来管理事务。49. Spring框架的事务管理有哪些优点?它为不同的事务API 如 JTA,JDBC,Hibernate,JPA 和JDO,提供一个不变的编程模式。它为编程式事务管理提供了一套简单的API而不是一些复杂的事务API如它支持声明式事务管理。它和Spring各种数据访问抽象层很好得集成。50. 你更倾向用那种事务管理类型?大多数Spring框架的用户选择声明式事务管理,因为它对应用代码的影响最小,因此更符合一个无侵入的轻量级容器的思想。声明式事务管理要优于编程式事务管理,虽然比编程式事务管理(这种方式允许你通过代码控制事务)少了一点灵活性。Spring面向切面编程(AOP)51. 解释AOP面向切面的编程,或AOP, 是一种编程技术,允许程序模块化横向切割关注点,或横切典型的责任划分,如日志和事务管理。52. Aspect 切面AOP核心就是切面,它将多个类的通用行为封装成可重用的模块,该模块含有一组API提供横切功能。比如,一个日志模块可以被称作日志的AOP切面。根据需求的不同,一个应用程序可以有若干切面。在Spring AOP中,切面通过带有@Aspect注解的类实现。53. 在Spring AOP 中,关注点和横切关注的区别是什么?关注点是应用中一个模块的行为,一个关注点可能会被定义成一个我们想实现的一个功能。横切关注点是一个关注点,此关注点是整个应用都会使用的功能,并影响整个应用,比如日志,安全和数据传输,几乎应用的每个模块都需要的功能。因此这些都属于横切关注点。54. 连接点连接点代表一个应用程序的某个位置,在这个位置我们可以插入一个AOP切面,它实际上是个应用程序执行Spring AOP的位置。55. 通知通知是个在方法执行前或执行后要做的动作,实际上是程序执行时要通过SpringAOP框架触发的代码段。Spring切面可以应用五种类型的通知:before:前置通知,在一个方法执行前被调用。after: 在方法执行之后调用的通知,无论方法执行是否成功。after-returning: 仅当方法成功完成后执行的通知。after-throwing: 在方法抛出异常退出时执行的通知。around: 在方法执行之前和之后调用的通知。56. 切点切入点是一个或一组连接点,通知将在这些位置执行。可以通过表达式或匹配的方式指明切入点。57. 什么是引入?引入允许我们在已存在的类中增加新的方法和属性。58. 什么是目标对象?被一个或者多个切面所通知的对象。它通常是一个代理对象。也指被通知(advised)对象。59. 什么是代理?代理是通知目标对象后创建的对象。从客户端的角度看,代理对象和目标对象是一样的。60. 有几种不同类型的自动代理?BeanNameAutoProxyCreatorDefaultAdvisorAutoProxyCreatorMetadata autoproxying61. 什么是织入。什么是织入应用的不同点?织入是将切面和到其他应用类型或对象连接或创建一个被通知对象的过程。织入可以在编译时,加载时,或运行时完成。62. 解释基于XML Schema方式的切面实现。在这种情况下,切面由常规类以及基于XML的配置实现。63. 解释基于注解的切面实现在这种情况下(基于@AspectJ的实现),涉及到的切面声明的风格与带有java5标注的普通java类一致。Spring 的MVC64. 什么是Spring的MVC框架?Spring 配备构建Web 应用的全功能MVC框架。Spring可以很便捷地和其他MVC框架集成,如Struts,Spring 的MVC框架用控制反转把业务对象和控制逻辑清晰地隔离。它也允许以声明的方式把请求参数和业务对象绑定。65. DispatcherServletSpring的MVC框架是围绕DispatcherServlet来设计的,它用来处理所有的HTTP请求和响应。66. WebApplicationContextWebApplicationContext 继承了ApplicationContext 并增加了一些WEB应用必备的特有功能,它不同于一般的ApplicationContext ,因为它能处理主题,并找到被关联的servlet。67. 什么是Spring MVC框架的控制器?控制器提供一个访问应用程序的行为,此行为通常通过服务接口实现。控制器解析用户输入并将其转换为一个由视图呈现给用户的模型。Spring用一个非常抽象的方式实现了一个控制层,允许用户创建多种用途的控制器。说到这里顺便给大家推荐一个Java架构方面的交流学习社群:650385180,里面不仅可以交流讨论,还有面试经验分享以及免费的资料下载,包括Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。相信对于已经工作和遇到技术瓶颈的码友,在这个群里会有你需要的内容。68. @Controller 注解该注解表明该类扮演控制器的角色,Spring不需要你继承任何其他控制器基类或引用Servlet API。69. @RequestMapping 注解该注解是用来映射一个URL到一个类或一个特定的方处理法上。by:老李

September 5, 2018 · 2 min · jiezi

绝对干货!初学者也能看懂的DPDK解析

欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~本文由Willko发表于云+社区专栏一、网络IO的处境和趋势从我们用户的使用就可以感受到网速一直在提升,而网络技术的发展也从1GE/10GE/25GE/40GE/100GE的演变,从中可以得出单机的网络IO能力必须跟上时代的发展。1. 传统的电信领域IP层及以下,例如路由器、交换机、防火墙、基站等设备都是采用硬件解决方案。基于专用网络处理器(NP),有基于FPGA,更有基于ASIC的。但是基于硬件的劣势非常明显,发生Bug不易修复,不易调试维护,并且网络技术一直在发展,例如2G/3G/4G/5G等移动技术的革新,这些属于业务的逻辑基于硬件实现太痛苦,不能快速迭代。传统领域面临的挑战是急需一套软件架构的高性能网络IO开发框架。2. 云的发展私有云的出现通过网络功能虚拟化(NFV)共享硬件成为趋势,NFV的定义是通过标准的服务器、标准交换机实现各种传统的或新的网络功能。急需一套基于常用系统和标准服务器的高性能网络IO开发框架。3. 单机性能的飙升网卡从1G到100G的发展,CPU从单核到多核到多CPU的发展,服务器的单机能力通过横行扩展达到新的高点。但是软件开发却无法跟上节奏,单机处理能力没能和硬件门当户对,如何开发出与时并进高吞吐量的服务,单机百万千万并发能力。即使有业务对QPS要求不高,主要是CPU密集型,但是现在大数据分析、人工智能等应用都需要在分布式服务器之间传输大量数据完成作业。这点应该是我们互联网后台开发最应关注,也最关联的。二、Linux + x86网络IO瓶颈在数年前曾经写过《网卡工作原理及高并发下的调优》一文,描述了Linux的收发报文流程。根据经验,在C1(8核)上跑应用每1W包处理需要消耗1%软中断CPU,这意味着单机的上限是100万PPS(Packet Per Second)。从TGW(Netfilter版)的性能100万PPS,AliLVS优化了也只到150万PPS,并且他们使用的服务器的配置还是比较好的。假设,我们要跑满10GE网卡,每个包64字节,这就需要2000万PPS(注:以太网万兆网卡速度上限是1488万PPS,因为最小帧大小为84B《Bandwidth, Packets Per Second, and Other Network Performance Metrics》),100G是2亿PPS,即每个包的处理耗时不能超过50纳秒。而一次Cache Miss,不管是TLB、数据Cache、指令Cache发生Miss,回内存读取大约65纳秒,NUMA体系下跨Node通讯大约40纳秒。所以,即使不加上业务逻辑,即使纯收发包都如此艰难。我们要控制Cache的命中率,我们要了解计算机体系结构,不能发生跨Node通讯。从这些数据,我希望可以直接感受一下这里的挑战有多大,理想和现实,我们需要从中平衡。问题都有这些1.传统的收发报文方式都必须采用硬中断来做通讯,每次硬中断大约消耗100微秒,这还不算因为终止上下文所带来的Cache Miss。2.数据必须从内核态用户态之间切换拷贝带来大量CPU消耗,全局锁竞争。3.收发包都有系统调用的开销。4.内核工作在多核上,为可全局一致,即使采用Lock Free,也避免不了锁总线、内存屏障带来的性能损耗。5.从网卡到业务进程,经过的路径太长,有些其实未必要的,例如netfilter框架,这些都带来一定的消耗,而且容易Cache Miss。三、DPDK的基本原理从前面的分析可以得知IO实现的方式、内核的瓶颈,以及数据流过内核存在不可控因素,这些都是在内核中实现,内核是导致瓶颈的原因所在,要解决问题需要绕过内核。所以主流解决方案都是旁路网卡IO,绕过内核直接在用户态收发包来解决内核的瓶颈。Linux社区也提供了旁路机制Netmap,官方数据10G网卡1400万PPS,但是Netmap没广泛使用。其原因有几个:1.Netmap需要驱动的支持,即需要网卡厂商认可这个方案。2.Netmap仍然依赖中断通知机制,没完全解决瓶颈。3.Netmap更像是几个系统调用,实现用户态直接收发包,功能太过原始,没形成依赖的网络开发框架,社区不完善。那么,我们来看看发展了十几年的DPDK,从Intel主导开发,到华为、思科、AWS等大厂商的加入,核心玩家都在该圈子里,拥有完善的社区,生态形成闭环。早期,主要是传统电信领域3层以下的应用,如华为、中国电信、中国移动都是其早期使用者,交换机、路由器、网关是主要应用场景。但是,随着上层业务的需求以及DPDK的完善,在更高的应用也在逐步出现。DPDK旁路原理:图片引自Jingjing Wu的文档《Flow Bifurcation on Intel® Ethernet Controller X710/XL710》左边是原来的方式数据从 网卡 -> 驱动 -> 协议栈 -> Socket接口 -> 业务右边是DPDK的方式,基于UIO(Userspace I/O)旁路数据。数据从 网卡 -> DPDK轮询模式-> DPDK基础库 -> 业务用户态的好处是易用开发和维护,灵活性好。并且Crash也不影响内核运行,鲁棒性强。DPDK支持的CPU体系架构:x86、ARM、PowerPC(PPC)DPDK支持的网卡列表:https://core.dpdk.org/supported/,我们主流使用Intel 82599(光口)、Intel x540(电口)四、DPDK的基石UIO为了让驱动运行在用户态,Linux提供UIO机制。使用UIO可以通过read感知中断,通过mmap实现和网卡的通讯。UIO原理:要开发用户态驱动有几个步骤:1.开发运行在内核的UIO模块,因为硬中断只能在内核处理2.通过/dev/uioX读取中断3.通过mmap和外设共享内存五、DPDK核心优化:PMDDPDK的UIO驱动屏蔽了硬件发出中断,然后在用户态采用主动轮询的方式,这种模式被称为PMD(Poll Mode Driver)。UIO旁路了内核,主动轮询去掉硬中断,DPDK从而可以在用户态做收发包处理。带来Zero Copy、无系统调用的好处,同步处理减少上下文切换带来的Cache Miss。运行在PMD的Core会处于用户态CPU100%的状态网络空闲时CPU长期空转,会带来能耗问题。所以,DPDK推出Interrupt DPDK模式。Interrupt DPDK:图片引自David Su/Yunhong Jiang/Wei Wang的文档《Towards Low Latency Interrupt Mode DPDK》它的原理和NAPI很像,就是没包可处理时进入睡眠,改为中断通知。并且可以和其他进程共享同个CPU Core,但是DPDK进程会有更高调度优先级。六、DPDK的高性能代码实现1. 采用HugePage减少TLB Miss默认下Linux采用4KB为一页,页越小内存越大,页表的开销越大,页表的内存占用也越大。CPU有TLB(Translation Lookaside Buffer)成本高所以一般就只能存放几百到上千个页表项。如果进程要使用64G内存,则64G/4KB=16000000(一千六百万)页,每页在页表项中占用16000000 * 4B=62MB。如果用HugePage采用2MB作为一页,只需64G/2MB=2000,数量不在同个级别。而DPDK采用HugePage,在x86-64下支持2MB、1GB的页大小,几何级的降低了页表项的大小,从而减少TLB-Miss。并提供了内存池(Mempool)、MBuf、无锁环(Ring)、Bitmap等基础库。根据我们的实践,在数据平面(Data Plane)频繁的内存分配释放,必须使用内存池,不能直接使用rte_malloc,DPDK的内存分配实现非常简陋,不如ptmalloc。2. SNA(Shared-nothing Architecture)软件架构去中心化,尽量避免全局共享,带来全局竞争,失去横向扩展的能力。NUMA体系下不跨Node远程使用内存。3. SIMD(Single Instruction Multiple Data)从最早的mmx/sse到最新的avx2,SIMD的能力一直在增强。DPDK采用批量同时处理多个包,再用向量编程,一个周期内对所有包进行处理。比如,memcpy就使用SIMD来提高速度。SIMD在游戏后台比较常见,但是其他业务如果有类似批量处理的场景,要提高性能,也可看看能否满足。4. 不使用慢速API这里需要重新定义一下慢速API,比如说gettimeofday,虽然在64位下通过vDSO已经不需要陷入内核态,只是一个纯内存访问,每秒也能达到几千万的级别。但是,不要忘记了我们在10GE下,每秒的处理能力就要达到几千万。所以即使是gettimeofday也属于慢速API。DPDK提供Cycles接口,例如rte_get_tsc_cycles接口,基于HPET或TSC实现。在x86-64下使用RDTSC指令,直接从寄存器读取,需要输入2个参数,比较常见的实现:static inline uint64_trte_rdtsc(void){ uint32_t lo, hi; asm volatile ( “rdtsc” : “=a”(lo), “=d”(hi) ); return ((unsigned long long)lo) | (((unsigned long long)hi) << 32);}这么写逻辑没错,但是还不够极致,还涉及到2次位运算才能得到结果,我们看看DPDK是怎么实现:static inline uint64_trte_rdtsc(void){ union { uint64_t tsc_64; struct { uint32_t lo_32; uint32_t hi_32; }; } tsc; asm volatile(“rdtsc” : “=a” (tsc.lo_32), “=d” (tsc.hi_32)); return tsc.tsc_64;}巧妙的利用C的union共享内存,直接赋值,减少了不必要的运算。但是使用tsc有些问题需要面对和解决1) CPU亲和性,解决多核跳动不精确的问题2) 内存屏障,解决乱序执行不精确的问题3) 禁止降频和禁止Intel Turbo Boost,固定CPU频率,解决频率变化带来的失准问题5. 编译执行优化1) 分支预测现代CPU通过pipeline、superscalar提高并行处理能力,为了进一步发挥并行能力会做分支预测,提升CPU的并行能力。遇到分支时判断可能进入哪个分支,提前处理该分支的代码,预先做指令读取编码读取寄存器等,预测失败则预处理全部丢弃。我们开发业务有时候会非常清楚这个分支是true还是false,那就可以通过人工干预生成更紧凑的代码提示CPU分支预测成功率。#pragma once#if !__GLIBC_PREREQ(2, 3)# if !define __builtin_expect# define __builtin_expect(x, expected_value) (x)# endif#endif#if !defined(likely)#define likely(x) (__builtin_expect(!!(x), 1))#endif#if !defined(unlikely)#define unlikely(x) (_builtin_expect(!!(x), 0))#endif2) CPU Cache预取Cache Miss的代价非常高,回内存读需要65纳秒,可以将即将访问的数据主动推送的CPU Cache进行优化。比较典型的场景是链表的遍历,链表的下一节点都是随机内存地址,所以CPU肯定是无法自动预加载的。但是我们在处理本节点时,可以通过CPU指令将下一个节点推送到Cache里。API文档:https://doc.dpdk.org/api/rte…static inline void rte_prefetch0(const volatile void p){ asm volatile (“prefetcht0 %[p]” : : [p] “m” ((const volatile char *)p));}#if !defined(prefetch)#define prefetch(x) __builtin_prefetch(x)#endif…等等3) 内存对齐内存对齐有2个好处:l 避免结构体成员跨Cache Line,需2次读取才能合并到寄存器中,降低性能。结构体成员需从大到小排序和以及强制对齐。参考《Data alignment: Straighten up and fly right》#define __rte_packed attribute((packed))l 多线程场景下写产生False sharing,造成Cache Miss,结构体按Cache Line对齐#ifndef CACHE_LINE_SIZE#define CACHE_LINE_SIZE 64#endif#ifndef aligined#define aligined(a) attribute((aligned(a)))#endif4) 常量优化常量相关的运算的编译阶段完成。比如C++11引入了constexp,比如可以使用GCC的__builtin_constant_p来判断值是否常量,然后对常量进行编译时得出结果。举例网络序主机序转换#define rte_bswap32(x) ((uint32_t)(__builtin_constant_p(x) ? rte_constant_bswap32(x) : rte_arch_bswap32(x)))其中rte_constant_bswap32的实现#define RTE_STATIC_BSWAP32(v) ((((uint32_t)(v) & UINT32_C(0x000000ff)) << 24) | (((uint32_t)(v) & UINT32_C(0x0000ff00)) << 8) | (((uint32_t)(v) & UINT32_C(0x00ff0000)) >> 8) | (((uint32_t)(v) & UINT32_C(0xff000000)) >> 24))5)使用CPU指令现代CPU提供很多指令可直接完成常见功能,比如大小端转换,x86有bswap指令直接支持了。static inline uint64_t rte_arch_bswap64(uint64_t _x){ register uint64_t x = _x; asm volatile (“bswap %[x]” : [x] “+r” (x) ); return x;}这个实现,也是GLIBC的实现,先常量优化、CPU指令优化、最后才用裸代码实现。毕竟都是顶端程序员,对语言、编译器,对实现的追求不一样,所以造轮子前一定要先了解好轮子。Google开源的cpu_features可以获取当前CPU支持什么特性,从而对特定CPU进行执行优化。高性能编程永无止境,对硬件、内核、编译器、开发语言的理解要深入且与时俱进。七、DPDK生态对我们互联网后台开发来说DPDK框架本身提供的能力还是比较裸的,比如要使用DPDK就必须实现ARP、IP层这些基础功能,有一定上手难度。如果要更高层的业务使用,还需要用户态的传输协议支持。不建议直接使用DPDK。目前生态完善,社区强大(一线大厂支持)的应用层开发项目是FD.io(The Fast Data Project),有思科开源支持的VPP,比较完善的协议支持,ARP、VLAN、Multipath、IPv4/v6、MPLS等。用户态传输协议UDP/TCP有TLDK。从项目定位到社区支持力度算比较靠谱的框架。腾讯云开源的F-Stack也值得关注一下,开发更简单,直接提供了POSIX接口。Seastar也很强大和灵活,内核态和DPDK都随意切换,也有自己的传输协议Seastar Native TCP/IP Stack支持,但是目前还未看到有大型项目在使用Seastar,可能需要填的坑比较多。我们GBN Gateway项目需要支持L3/IP层接入做Wan网关,单机20GE,基于DPDK开发。问答如何检查网络连接?相关阅读把报文再扔回内核,DPDK这样做用DPDK rte_ring实现多进程间通信低于0.01%的极致Crash率是怎么做到的? 【每日课程推荐】新加坡南洋理工大学博士,带你深度学习NLP技术 ...

September 5, 2018 · 2 min · jiezi

聊聊sentinel的SystemSlot

序本文主要研究一下sentinel的SystemSlotSystemSlotsentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/system/SystemSlot.javapublic class SystemSlot extends AbstractLinkedProcessorSlot<DefaultNode> { @Override public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, Object… args) throws Throwable { SystemRuleManager.checkSystem(resourceWrapper); fireEntry(context, resourceWrapper, node, count, args); } @Override public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object… args) { fireExit(context, resourceWrapper, count, args); }}这里是通过SystemRuleManager.checkSystem(resourceWrapper)进行系统限流判断SystemRuleManager public static void checkSystem(ResourceWrapper resourceWrapper) throws BlockException { // 确定开关开了 if (checkSystemStatus.get() == false) { return; } // for inbound traffic only if (resourceWrapper.getType() != EntryType.IN) { return; } // total qps double currentQps = Constants.ENTRY_NODE == null ? 0.0 : Constants.ENTRY_NODE.successQps(); if (currentQps > qps) { throw new SystemBlockException(resourceWrapper.getName(), “qps”); } // total thread int currentThread = Constants.ENTRY_NODE == null ? 0 : Constants.ENTRY_NODE.curThreadNum(); if (currentThread > maxThread) { throw new SystemBlockException(resourceWrapper.getName(), “thread”); } double rt = Constants.ENTRY_NODE == null ? 0 : Constants.ENTRY_NODE.avgRt(); if (rt > maxRt) { throw new SystemBlockException(resourceWrapper.getName(), “rt”); } // 完全按照RT,BBR算法来 if (highestSystemLoadIsSet && getCurrentSystemAvgLoad() > highestSystemLoad) { if (currentThread > 1 && currentThread > Constants.ENTRY_NODE.maxSuccessQps() * Constants.ENTRY_NODE.minRt() / 1000) { throw new SystemBlockException(resourceWrapper.getName(), “load”); } } } public static double getCurrentSystemAvgLoad() { return statusListener.getSystemAverageLoad(); }先判断qps,在判断总线程数、之后判断rt,最后判断系统负载有没有超过限制StatisticNodesentinel-core/src/main/java/com/alibaba/csp/sentinel/node/StatisticNode.java @Override public long successQps() { return rollingCounterInSecond.success() / IntervalProperty.INTERVAL; } @Override public int curThreadNum() { return curThreadNum.get(); } @Override public long avgRt() { long successCount = rollingCounterInSecond.success(); if (successCount == 0) { return 0; } return rollingCounterInSecond.rt() / successCount; } @Override public long maxSuccessQps() { return rollingCounterInSecond.maxSuccess() * SampleCountProperty.sampleCount; } @Override public long minRt() { return rollingCounterInSecond.minRt(); }successQps、curThreadNum、avgRt、maxSuccessQps、minRt指标在StatisticNode上进行维护SystemStatusListenersentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/system/SystemStatusListener.javapublic class SystemStatusListener implements Runnable { volatile double currentLoad = -1; volatile String reason = StringUtil.EMPTY; static final int processor = ManagementFactory.getOperatingSystemMXBean().getAvailableProcessors(); public double getSystemAverageLoad() { return currentLoad; } @Override public void run() { try { if (!SystemRuleManager.getCheckSystemStatus()) { return; } // system average load OperatingSystemMXBean operatingSystemMXBean = ManagementFactory.getOperatingSystemMXBean(); currentLoad = operatingSystemMXBean.getSystemLoadAverage(); StringBuilder sb = new StringBuilder(); if (currentLoad > SystemRuleManager.getHighestSystemLoad()) { sb.append(“load:”).append(currentLoad).append(";"); sb.append(“qps:”).append(Constants.ENTRY_NODE.passQps()).append(";"); sb.append(“rt:”).append(Constants.ENTRY_NODE.avgRt()).append(";"); sb.append(“thread:”).append(Constants.ENTRY_NODE.curThreadNum()).append(";"); sb.append(“success:”).append(Constants.ENTRY_NODE.successQps()).append(";"); sb.append(“minRt:”).append(Constants.ENTRY_NODE.minRt()).append(";"); sb.append(“maxSuccess:”).append(Constants.ENTRY_NODE.maxSuccessQps()).append(";"); RecordLog.info(sb.toString()); } } catch (Throwable e) { RecordLog.info(“could not get system error “, e); } }}系统负载是通过SystemRuleManager定时调度SystemStatusListener,通过OperatingSystemMXBean去获取 static { checkSystemStatus.set(false); statusListener = new SystemStatusListener(); scheduler.scheduleAtFixedRate(statusListener, 5, 1, TimeUnit.SECONDS); currentProperty.addListener(listener); } 小结sentinel的SystemSlot是通过判断系统相关指标来进行限流,主要的指标为qps、总线程数、rt、系统负载。docSystemSlot ...

September 5, 2018 · 2 min · jiezi

js中reduce的神奇用法

js中reduce的神奇用法最近经常在项目中经常看到别人用reduce处理数据,很是牛掰,很梦幻, 不如自己琢磨琢磨。先看w3c语法array.reduce(function(total, currentValue, currentIndex, arr), initialValue);/* total: 必需。初始值, 或者计算结束后的返回值。 currentValue: 必需。当前元素。 currentIndex: 可选。当前元素的索引; arr: 可选。当前元素所属的数组对象。 initialValue: 可选。传递给函数的初始值,相当于total的初始值。*/常见用法数组求和const arr = [12, 34, 23];const sum = arr.reduce((total, num) => total + num);<!– 设定初始值求和 –>const arr = [12, 34, 23];const sum = arr.reduce((total, num) => total + num, 10); // 以10为初始值求和<!– 对象数组求和 –>var result = [ { subject: ‘math’, score: 88 }, { subject: ‘chinese’, score: 95 }, { subject: ’english’, score: 80 }];const sum = result.reduce((prev, cur) => prev + cur.score, 0); const sum = result.reduce((prev, cur) => prev + cur.score, -10); // 总分扣除10分数组最大值const a = [23,123,342,12];const max = a.reduce(function(pre,cur,inde,arr){return pre>cur?pre:cur;}); // 342进阶用法数组对象中的用法<!– 比如生成“老大、老二和老三” –>const objArr = [{name: ‘老大’}, {name: ‘老二’}, {name: ‘老三’}];const res = objArr.reduce((pre, cur, index, arr) => { if (index === 0) { return cur.name; } else if (index === (arr.length - 1)) { return pre + ‘和’ + cur.name; } else { return pre + ‘、’ + cur.name; }}, ‘’);求字符串中字母出现的次数const str = ‘sfhjasfjgfasjuwqrqadqeiqsajsdaiwqdaklldflas-cmxzmnha’;const res = str.split(’’).reduce((prev, cur) => {prev[cur] ? prev[cur]++ : prev[cur] = 1; return prev;}, {});数组转数组<!– 按照一定的规则转成数组 –>var arr1 = [2, 3, 4, 5, 6]; // 每个值的平方var newarr = arr1.reduce((prev, cur) => {prev.push(cur * cur); return prev;}, []);数组转对象<!– 按照id 取出stream –>var streams = [{name: ‘技术’, id: 1}, {name: ‘设计’, id: 2}];var obj = streams.reduce((prev, cur) => {prev[cur.id] = cur; return prev;}, {});高级用法多维的叠加执行操作<!– 各科成绩占比重不一样, 求结果 –>var result = [ { subject: ‘math’, score: 88 }, { subject: ‘chinese’, score: 95 }, { subject: ’english’, score: 80 }];var dis = { math: 0.5, chinese: 0.3, english: 0.2};var res = result.reduce((prev, cur) => dis[cur.subject] * cur.score + prev, 0);<!– 加大难度, 商品对应不同国家汇率不同,求总价格 –>var prices = [{price: 23}, {price: 45}, {price: 56}];var rates = { us: ‘6.5’, eu: ‘7.5’,};var initialState = {usTotal:0, euTotal: 0};var res = prices.reduce((prev1, cur1) => Object.keys(rates).reduce((prev2, cur2) => { console.log(prev1, cur1, prev2, cur2); prev1[${cur2}Total] += cur1.price * rates[cur2]; return prev1;}, {}), initialState);var manageReducers = function() { return function(state, item) { return Object.keys(rates).reduce((nextState, key) => { state[${key}Total] += item.price * rates[key]; return state; }, {}); }};var res1= prices.reduce(manageReducers(), initialState);扁平一个多维数组var arr = [[1, 2, 8], [3, 4, 9], [5, 6, 10]];var res = arr.reduce((x, y) => x.concat(y), []);对象数组去重const hash = {}; chatlists = chatlists.reduce((obj, next: Object) => { const hashId = ${next.topic}_${next.stream_id}; if (!hash[hashId]) { hash[${next.topic}_${next.stream_id}] = true; obj.push(next); } return obj; }, []); ...

September 5, 2018 · 2 min · jiezi

原来你是这样的http2......

欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~本文由mariolu发表于云+社区专栏序言目前HTTP/2.0(简称h2)已经在广泛使用(截止2018年8月根据Alexa流行度排名的头部1千万网站中,h2占比约29%,https://w3techs.com/technolog…)。写此文章的目的是:h2作为较新的技术,并逐渐占有率广泛,虽然目前有更新的QUIC,但其实现思路类似于h2。颠覆以往的HTTP/1.x,H2的创造性的技术值得我们细细品味。此篇文章根据笔者在h2开发经验和思考,向你介绍全面的h2知识以及是非功过。本篇更注重于帮助读者理解h2的设计思路、亦可作为一篇RFC导读或者总结。第一话、追踪溯源图1、HTTP年鉴图早在1991年,伴随WWW诞生之初,HTTP/0.9协议已经提出。HTTP0.9是简单且应用受限的协议。支持去网络主机获取对应路径的资源。但是没有扩展属性。其协议之简单甚至只用下面一个访问谷歌主机的例子概括了HTTP/0.9的全部。如下所示,协议只支持GET,没有http头;响应只能是超文本。telnet google.com 80Connected to x.x.x.xGET /about(Hyper text)(Conection closed)随着人们对富媒体信息的渴望以及浏览器的普及,HTTP/1.0在1996年被提出来。HTTP/1.0的很多特性目前还被广泛使用,但是仍然像HTTP/0.9一样一次请求需要创建一次的tcp连接。随即短短几年时间内,HTTP/1.1以RFC标准形式再次展现在人们眼前。此时的HTTP协议1.1版本已经重新设计了长连接、options请求方法、cache头、upgrade头、range头、transfer-encoding头, 以及pieline(in order)等概念。而我们另一个所熟知的HTTPS的SSL/TLS技术各个版本差不多在后来的十年间逐渐被提出。出于安全考虑,互联网通信间的防火墙路由交换机等设备,这些设备一般仅会开发有限的端口(如80和443)。各种版本的通信协议只能复用这些端口。HTTP1.1的80端口设计了upgrade请求头升级到更高级的协议,而443端口为了避免多消耗个网络RTT,在tls握手过程中使用了NPN/ALPN技术直接在通信之前保持CS两端的协议一致。NPN/ALPN是是TLS协议扩展,其中NPN是Google为实现spdy提出的。由服务端提供可支持的协议,供客户端选择。ALPN则是更接近于HTTP交互的方式,由客户端先发出使用某种协议的请求,由服务端确认是否支持协议。ALPN为了HTTP2诞生做铺垫。另外一个不得不提的是spdy协议。Spdy旨在解决HTTP1.1的线头阻塞问题(后面章节有详细讨论)于2009被google提出。同时分别于2012提出了spdy3.0实现了流控制,2013-2014期间提出了流优先级,server push等概念。Spdy的存在意义更像是http2.0的体验服。它为探索HTTP继续演进道路做了铺垫。第二话、人机交互汇编是效率最高的语言之一,但是又是最晦涩难懂的语言之一。而人脑易懂的编程语言往往牺牲性能作为折衷。简单说,HTTP/1.x协议就是为了人类语言习惯所设计的协议,但是转换成机器执行协议并不是高效的。让我们在回顾下计算机执行解析HTTP1.x的流程。GET / HTTP/1.1<crlf>Host: xxx.aa.com<crlf><crlf>对应的解析伪代码是loop while(! CRLF) read bytes end while if line 1: parse line as Request-Line else if empty line: Break out and We have done else If start with non-whitespace parse header else if space continue with last heade end ifend loop在伪代码解析流程可以看到,肉眼看起来简洁的协议解析起来是这么的费劲。而且在HTTP服务器中还要考虑这种问题:字节行的长度是未知的,也不知道预先分配多大内存。HTTP/2.0使用了计算机易懂的二进制编码信息,而且得向上兼容HTTP的涵义。具体我们来看下他是如何做到的。像大多数通信协议一样,桢是传输最小单位。桢分为数据帧和控制桢。数据帧作为数据的载体,控制桢控制信道的信令。h2桢的通用格式为首部9字节+额外的字符。正如你能想到的那样,桢的第一个部分是描述长度,第二个部分描述了桢的类型,第三个部分描述了标志Flag,第四个部分是唯一序列号。这是所有桢的通用头。通用头紧接的是桢的实体。图4展示了桢的结构。图2、通用桢的格式这样设计有什么好处呢。再来看一下桢的解析流程,你就会发现对计算机来说更简洁。loop read 9 byte payload_Length=first 3 bytes read payload swith type: Take actionend loopHTTP2.0使用header桢表达HTTP header+request line,data桢表达Body。header桢和data桢使用相同的stream id组成一个完整的HTTP请求/响应包。这里的stream描述了一次请求和响应,相当于完成了一次HTTP/1.x的短连接请求和响应。第三话、并行不悖上节讲到我们用h2桢完整表达了HTTP/1.x。但是h2协议抱负远不止于此。它的真正目的是解决之前HTTP1.x的线头阻塞问题、改善网络延迟和页面加载时间。我们知道一个完整的网页包含了主页请求和数次或数十次的子请求。HTTP/1.1已经可以并行发出所有请求.但是HTTP本身是无状态的协议,它依赖于时间的顺序来识别请求和响应直接的对应关系。先来的请求必须先给响应。那么如果后面的响应资源对浏览器构建DOM或者CSSOM更重要。那它必须阻塞等待前者完成。当然这也难不倒我们,我们可以多开几条tcp连接(浏览器规定一个origin(协议+host+port)最多6个)或者合并资源来减少不必要的阻塞。这是有代价的。首先tcp建连的开销,其实合并资源带来一小块子资源过期导致整个合并资源的缓存过期。对此,h2有一揽子的解决方案,接下来一一道来。h2在一个tcp连接创建多个流。每个流可以有从属关系,比如说根据浏览器加载的优先级顺序(主请求>CSS>能改变DOM结构的JS文件>图片和字体资源文件)建立一条依赖关系链。处于同一等级的依赖关系中可以设置权重。权重用于分配传输信道资源多少。图6例子说明了有一次主页请求index.html、一次main.css,一次jq.js以及一些image文件和字体文件qq.tff图3、h2请求的依赖树HTML的优先级最高,在HTML传输完成之前,其他文件不会被传输。HTML传输完成后,JS和CSS根据其分配的权重占比分配信息传输资源。如果CSS传输完成后,TFF和PNG如果是相同权重,那么他们将占有1/4的信道资源。这里抛出3个问题和答案。如果CSS被阻塞了,那么 JS 得到本属于CSS的通信资源如果CSS传输完成但没有被移依赖树, TFF和PNG继承CSS的通信份额 (假设TFF和PNG权重一样,那么各分得1/4通信资源).如果CSS在依赖数被移除,JS, TFF, PNG平分通信资源(假设3个权重一样,那么三者各分得1/3通信资源)第四话、众星捧月HTTP2还设计了一系列方案来改善网络的性能、包括流量控制,HPack压缩,Server Push。什么你说TCP已经有流量控制了,HTTP不是多此一举吗?没错,但是在单条TCP内部,各个流可是没有流量控制。流量控制使用了Update Frame不断告知发送方更新发送的窗口大小(上限)。流量控制一个现实用途是阻塞不重要的请求,以腾出更大的通信资源给重要的请求使用。流量控制是不可以被关闭的,流量大小可以设置2的31次方-1(2GB)。不同的中间网络设备有不一样的吞吐能力。流控的另一个用途在于同步所有的中间设备交换机最小的上限。流量窗口初始大小为65535(2的16次方-1)。就像世界上的大多数财富聚集在少数人身上一样。在一份对48,452,989个请求的统计中,以下11个头占据了99%的数量,依名次递减分别是user-agent、accetp-encoding、accept-language、accept、referer、host、connection、cookie、origin、upgrade-inseure-request、content-type。http header的值也有很大相似性,比如说”/index.html“, “gzip, deflate”。Cookie也携带冗余的信息。这些都组成了http header大量可以压缩的内容。而在一份GET请求和一个304响应或者content-length很少的响应中,这些头占据了很大比例的通信资源。2016年发布的一份HTTP报告中,请求头大约在460bytes,对一个通常的网页,平均会有140个请求对象。这些头总共需要63KB。这些量很有可能会是首屏和页面加载时间优化的瓶颈。可能你会说用gzip等压缩算法这些请求头,不就完了吗?的确spdy就这样干过,直到2013年BREACH攻击暴露了gzip压缩在https应用的安全性,这种攻击让攻击者很容易获得session cookie等数据。于是才有了HPACK。HPACK简单来说就是索引表,包括静态表和动态表。静态表由RFC定义,从不改变,静态表预留了62个表项。每个连接的通信双方维护着动态表。H2协议使用索引号代表http中的name、value或者name-value。假设被索引的是name,value没有索引,那么value还可以用霍夫曼编码压缩。在预定的头字段静态映射表 中已经有预定义的 Header Name 和 Header Value值,这时候的二进制数据格式如图4, 第一位固定为1, 后面7位为映射的索引值。图8的83就是这样的,83的二进制字节标示1000 0011,抹掉首位就是 3 , 对应的静态映射表中的method:POST。图4、index索引name和value图5、抓包示意预定的头字段静态映射表中有 name,需要设置新值。图6所示例子,一个指定 path的Header,首字符 为 44 ,对应的二进制位0100 0100。前两个字符为 01 ,Index 为 4 ,即对应静态映射表中的 path 头。第二个字符为 95对应的二进制位 1001 0101,排除首字符对应的 Value Length 为 十进制的21。即 算上 44,一共23个字符来记录这个信息。图6、index索引name和自定义value图7、抓包示意预定的头字段静态映射表中没有 name,需要设置新name和新值。40 的二进制是0100 0000,02 的二进制是0000 0010,后七位的十进制值是 2,86 的二进制是1000 0110,后7位的十进制值是6。图8、index索引自定义name和自定义value图9、抓包示意明确要求该请求头不做hpack的indexHTTP2.0还有个大杀器是Server Push,Server Push利用闲置的带宽资源可以向浏览器预推送页面展示的关键资源,Server push有效得降低了页面加载时间。具体详情参考笔者的另一篇文章https://cloud.tencent.com/dev…。第五话、庖丁解牛HTTP2相比HTTP1更适合计算机执行。但是其二进制特性不易于人脑理解。这一话我们专门来讲讲关于http2的调试工具。Chrome(qq浏览器)可以按住F12查看h2协议。图13所示为浏览器网络时序图,列出了具体协议名称。chrome还可以在地址栏敲入chrome://net-internals/#http2查看到h2协议细节,如图11所示。点击相应的host就可以看到h2协商过程,如图12所示。图10、浏览器的网络时序图chrome://net-internals/#http2图11、chrome调试h2图12、h2协商过程wireshark(需和chrome或firefox搭配使用)。设置环境变量SSLKEYLOGFILE=c:tempsslkeylog.log。然后在Wireshark->Preferences->Protocols->SSL配置key所在路径。图13、wireshark配置图14、wireshark抓h2包Nghttp2是一个完整的http2协议实现的组件。作者也参与过spdy实现。目前nghttp2库被很多知名软件作为h2协议实现库使用。另外nghttp2也自带了h2协议的分析工具。图18展示了在明文状态使用upgrade头升级到h2c。图19展示了在https基础上升级到h2。图15、明文状态使用upgrade头升级到h2c图16、展示了在https基础上升级到h2Curl的—http2选项(需要和nghttp2一起编译)图17、支持h2的curl客户端调试Github还有些实用的http2工具组件,诸如chrome-http2-log-parser、http2-push-manifest等组件,笔者后续会专门开篇文章介绍这些工具。对于移动端的调试,ios可以用charles proxy做代理,android需要开发者模式使用移动端的chrome,笔者在移动端使用较少,这里就不做展开。第六话、雕栏玉砌H2怎么部署呢,目前主流服务端像nginx、apache都已经支持http2,主流的客户端curl和各种浏览器(包括移动端safari和chrome-android)基本也支持http2。代理服务器如ATS、Varnish,Akamai、腾讯云等CDN服务也支持http2。那么怎么把一套网站部署到h2。或者说部署h2网站和之前h1网站有什么不一样?如果是自己的源站,那么请确保服务器支持TLS1.2已经RFC7540所要求的加密套件,h2需要保证支持alpn。你可以使用ssllabs等网站检查。对于h2服务器的要求是h2必须了解如何设置流的优先级,h2服务器需要支持server push。h2客户端需要尽量多的发送请求。如果你的网站是从http1.x迁移过来的,那么之前对于http1.x所做的优化可能无任何帮助甚至更差。合并小文件不在需要,因为额外的小文件请求在h2看来只是开销很少。并且如果大文件的局部更改使得整个大文件缓存失效。在http1.0时代使用多个域名来并发http连接,在http2也毫无必要,因为http2天生就是并发的。http1.x做的优化比如说图片资源文件不使用cookie来减少请求大小,http2的header压缩功能也减少了这种影响。即使不做这种优化也亦可。像合并css、小图片带来的增益在http2.0也是可忽略的。如果网页使用第三方网站组件,那么请尽可能减少使用第三方网站组件。第三方网站不能保证支持h2,所以它可能成为木桶理论的最大短板。谨慎使用2.0-1.x的部署方案,h2流转化成h1请求。因为这样无法发挥h2性能。图18、2.0-1.x的部署方案CDN代理服务器的h2支持,可以屏蔽h2强制走tls的代理服务器。如图19,代理可以在与各种协议客户端的网络环境下,切断和客户端的tls连接,和服务器新建连接。也可以作为load balancer,相当于HTTP2.0用户和HTTP2.x服务器直接通信。图19、带tls客户端功能的代理图20列举如果绕过proxy到达h2服务器。此时的proxy相当于tcp转发的load balance功能的设备。如果该proxy支持tls的alpn协议,那么它也可以选择HTTP代理功能,和h2服务器可以建立加密连接。如果即不支持alpn,也不支持tcp转发。那么proxy只能用upgrade升级成h2协议。图20、经过代理服务器的H2部署方案第七话、十全九美HTTP2.0是建立在TCP之上,所以TCP的所有缺点他都有,所以H2能发挥最大性能得益于调优的tcp协议栈。TCP的慢启动特性,决定h2一开始的并发流量不会太大,TCP以及SSL的握手连接也会拖慢h2的首包网络耗时。QUIC则完全地抛弃TCP,在UDP基础上实现了HTTP2的一系列特性。同时做了应用层的如TCP的可靠性保障。同时这些TLS1.3传输更快更简洁。这些都为HTTP2.0进化到HTTP3.0提供了一些思路。总结以上内容都来源于笔者的实践经验和理论总结。篇幅所限不能涵盖各个细节。具体可以继续参考RFC7540和RFC7541协议。问答没有“http | https”的网址怎么实现?相关阅读我是怎么一步步用go找出压测性能瓶颈HTTP/2之服务器推送(Server Push)最佳实践低于0.01%的极致Crash率是怎么做到的? 【每日课程推荐】新加坡南洋理工大学博士,带你深度学习NLP技术 ...

September 5, 2018 · 1 min · jiezi