Quantcast
Channel: IT瘾博客推荐
Viewing all 532 articles
Browse latest View live

Windows下编译ncnn的android端的库 - 迷若烟雨的专栏 - CSDN博客

$
0
0

ncnn是腾讯开源的一个为手机端极致优化的高性能神经网络前向计算框架,目前已在腾讯多款应用中使用。

由于开发者使用的是linux类似的环境,因此只提供了build.sh用来构建android和iOS的库,但好在提供了CMakelist.txt文件,我们可以借助CMake进行跨平台的交叉编译。

将以下代码存为build.bat文件,双击执行即可

@echo off
set ANDROID_NDK=D:/AndroidSDK/ndk-bundle

mkdir build_android
cd build_android
cmake -G "Unix Makefiles" -DCMAKE_TOOLCHAIN_FILE="%ANDROID_NDK%/build/cmake/android.toolchain.cmake" ..\ -DCMAKE_MAKE_PROGRAM="%ANDROID_NDK%/prebuilt/windows-x86_64/bin/make.exe" -DCMAKE_BUILD_TYPE=Release -DANDROID_ABI="armeabi-v7a" -DANDROID_NATIVE_API_LEVEL=9
cmake --build .
cmake --build . --target install
cd ..
pause

其中ANDROID_NDK要换成你本机android ndk所在的目录,没有的话就去搜索下一个,最好15c版本及以上。


微服务架构之「 访问安全 」 - 不止思考 - CSDN博客

$
0
0

应用程序的访问安全又是我们每一个研发团队都必须关注的重点问题。尤其是在我们采用了微服务架构之后,项目的复杂度提升了N个级别,相应的,微服务的安全工作也就更难更复杂了。并且我们以往擅长的单体应用的安全方案对于微服务来说已经不再适用了。我们必须有一套新的方案来保障微服务架构的安全。

在探索微服务访问安全之前,我们还是先来回顾一下单体应用的安全是如何实现的。

一、传统单体应用如何实现「访问安全」?

下图就是一个传统单体应用的访问示意图:

(图片来自WillTran在slideshare分享)

在应用服务器里面,我们有一个auth模块(一般采用过滤来实现),当有客户端请求进来时,所有的请求都必须首先经过这个auth来做身份验证,验证通过后,才将请求发到后面的业务逻辑。

通常客户端在第一次请求的时候会带上身份校验信息(用户名和密码),auth模块在验证信息无误后,就会返回Cookie存到客户端,之后每次客户端只需要在请求中携带Cookie来访问,而auth模块也只需要校验Cookie的合法性后决定是否放行。

可见,在传统单体应用中的安全架构还是蛮简单的,对外也只有一个入口,通过auth校验后,内部的用户信息都是内存/线程传递,逻辑并不是复杂,所以风险也在可控范围内。

那么,当我们的项目改为微服务之后,「访问安全」又该怎么做呢。

二、微服务如何实现「访问安全」?

在微服务架构下,有以下三种方案可以选择,当然,用的最多的肯定还是OAuth模式。

  • 网关鉴权模式(API Gateway)

  • 服务自主鉴权模式

  • API Token模式(OAuth2.0)

下面分别来讲一下这三种模式:

  1. 网关鉴权模式(API Gateway)

    (图片来自WillTran在slideshare分享)

    通过上图可见,因为在微服务的最前端一般会有一个API网关模块(API Gateway),所有的外部请求访问微服务集群时,都会首先通过这个API Gateway,所以我们可以在这个模块里部署auth逻辑,实现统一集中鉴权,鉴权通过后,再把请求转发给后端各个服务。

    这种模式的优点就是,由API Gateway集中处理了鉴权的逻辑,使得后端各微服务节点自身逻辑就简单了,只需要关注业务逻辑,无需关注安全性事宜。

    这个模式的问题就是,API Gateway适用于身份验证和简单的路径授权(基于URL的),对于复杂数据/角色的授权访问权限,通过API Gateway很难去灵活的控制,毕竟这些逻辑都是存在后端服务上的,并非存储在API Gateway里。

  2. 服务自主鉴权模式

    (图片来自WillTran在slideshare分享)

    服务自主鉴权就是指不通过前端的API Gateway来控制,而是由后端的每一个微服务节点自己去鉴权。

    它的优点就是可以由更为灵活的访问授权策略,并且相当于微服务节点完全无状态化了。同时还可以避免API Gateway 中 auth 模块的性能瓶颈。

    缺点就是由于每一个微服务都自主鉴权,当一个请求要经过多个微服务节点时,会进行重复鉴权,增加了很多额外的性能开销。

  3. API Token模式(OAuth2.0)

    (图片来自网络)

    如图,这是一种采用基于令牌Token的授权方式。在这个模式下,是由授权服务器(图中Authorization Server)、API网关(图中API Gateway)、内部的微服务节点几个模块组成。

    流程如下:

    第一步:客户端应用首先使用账号密码或者其它身份信息去访问授权服务器(Authorization Server)获取 访问令牌(Access Token)。

    第二步:拿到访问令牌(Access Token)后带着它再去访问API网关(图中API Gateway),API Gateway自己是无法判断这个Access Token是否合法的,所以走第三步。

    第三步:API Gateway去调用Authorization Server校验一下Access Token的合法性。

    第四步:如果验证完Access Token是合法的,那API Gateway就将Access Token换成JWT令牌返回。

    (注意:此处也可以不换成JWT而是直接返回原Access Token。但是换成JWT更好,因为Access Token是一串不可读无意义的字符串,每次验证Access Token是否合法都需要去访问Authorization Server才知道。但是JWT令牌是一个包含JOSN对象,有用户信息和其它数据的一个字符串,后面微服务节点拿到JWT之后,自己就可以做校验,减少了交互次数)。

    第五步:API Gateway有了JWT之后,就将请求向后端微服务节点进行转发,同时会带上这个JWT。

    第六步:微服务节点收到请求后,读取里面的JWT,然后通过加密算法验证这个JWT,验证通过后,就处理请求逻辑。

    这里面就使用到了OAuth2.0的原理,不过这只是OAuth2.0各类模式中的一种。

由于OAuth2.0目前最为常用,所以接下来我再来详细讲解一下OAuth2.0的原理和各类用法。

三、详解 OAuth2.0 的「 访问安全 」?

OAuth2.0是一种访问授权协议框架。它是基于Token令牌的授权方式,在不暴露用户密码的情况下,使 应用方 能够获取到用户数据的访问权限。

例如:你开发了一个视频网站,可以采用第三方微信登陆,那么只要用户在微信上对这个网站授权了,那这个网站就可以在无需用户密码的情况下获取用户在微信上的头像。

OAuth2.0 的流程如下图:

OAuth2.0 里的主要名词有:

  • 资源服务器:用户数据/资源存放的地方,在微服务架构中,服务就是资源服务器。在上面的例子中,微信头像存放的服务就是资源服务器。

  • 资源拥有者:是指用户,资源的拥有人。在上面的例子中某个微信头像的用户就是资源拥有者。

  • 授权服务器:是一个用来验证用户身份并颁发令牌的服务器。

  • 客户端应用:想要访问用户受保护资源的客户端/Web应用。在上面的例子中的视频网站就是客户端应用。

  • 访问令牌:Access Token,授予对资源服务器的访问权限额度令牌。

  • 刷新令牌:客户端应用用于获取新的 Access Token 的一种令牌。

  • 客户凭证:用户的账号密码,用于在 授权服务器 进行验证用户身份的凭证。

OAuth2.0有四种授权模式,也就是四种获取令牌的方式:授权码、简化式、用户名密码、客户端凭证。

下面来分别讲解一下:

  1. 授权码(Authorization Code)

    授权码模式是指:客户端应用先去申请一个授权码,然后再拿着这个授权码去获取令牌的模式。这也是目前最为常用的一种模式,安全性比较高,适用于我们常用的前后端分离项目。通过前端跳转的方式去访问 授权服务器 获取授权码,然后后端再用这个授权码访问 授权服务器 以获取 访问令牌。

    流程如上图。

    第一步,客户端的前端页面(图中UserAgent)将用户跳转到 授权服务器(Authorization Server)里进行授权,授权完成后,返回 授权码(Authorization Code)

    第二步,客户端的后端服务(图中Client)携带授权码(Authorization Code)去访问 授权服务器,然后获得正式的 访问令牌(Access Token)

    页面的前端和后端分别做不同的逻辑,前端接触不到Access Token,保证了Access Token的安全性。

  2. 简化式(Implicit)

    简化模式是在项目是一个纯前端应用,在没有后端的情况下,采用的一种模式。

    因为这种方式令牌是直接存在前端的,所以非常不安全,因此令牌的有限期设置就不能太长。

    其流程就是:

    第一步:应用(纯前端的应用)将用户跳转到 授权服务器(Authorization Server)里进行授权,授权完成后,授权服务器 直接将 Access Token 返回给 前端应用,令牌存储在前端页面。

    第二步:应用(纯前端的应用)携带 访问令牌(Access Token) 去访问资源,获取资源。

    在整个过程中,虽然令牌是在前端URL中直接传递,但注意,令牌在HTTP协议中不是放在URL参数字段中的,而是放在URL锚点里。因为锚点数据不会被浏览器发到服务器,因此有一定的安全保障。

  3. 用户名密码(Resource Owner Credentials)

    这种方式最容易理解了,直接使用用户的用户名/密码作为授权方式去访问 授权服务器,从而获取Access Token,这个方式因为需要用户给出自己的密码,所以非常的不安全性。一般仅在客户端应用与授权服务器、资源服务器是归属统一公司/团队,互相非常信任的情况下采用。

  4. 客户端凭证(Client Credentials)

    这是适用于服务器间通信的场景。客户端应用拿一个用户凭证去找授权服务器获取Access Token。

以上,就是对微服务架构中「访问安全」的一些思考。

在微服务架构的系列文章中,前面已经通过文章介绍过了「服务注册 」、「服务网关 」、「配置中心 」、「 监控系统 」、「调用链监控」、「容错隔离」,大家可以翻阅历史文章查看。

码字不易啊,喜欢的话不妨转发朋友,或点击文章右下角的“在看”吧。😊

本文原创发布于微信公众号「 不止思考 」,欢迎关注。涉及 思维认知、个人成长、架构、大数据、Web技术 等。 


 

Ubuntu下CPU/GPU模式YOLOv3代码运行-holygao的博客-51CTO博客

$
0
0

YOLO是近几年物体检测主要算法之一,2018年已发展到YOLOv3,是目前速度最快的物体检测算法,详细内容可查看 YOLO主页。YOLO的主要优势在于基于纯C语言编写的DarkNet,可查看 DarkNet主页,不需要其他依赖库,跨平台能力强,运行速度快,这里是 下载地址。有趣的是里边有好几个LICENSE文件,其中LICENSE.fuck的内容是这样的:

DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
               Version 2, December 2004

Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>

Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.

DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE

TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

  1. You just DO WHAT THE FUCK YOU WANT TO.

短短几行字出现了三次“FUCK”,还出现在标题中。这是我第一次见到这样的LICENSE文档,估计是往github上推送时忘了删除吧。
下面是我运行YOLOv3代码的过程。
我的运行环境为:

  • Ubuntu16.04LTS
  • CUDA9.0
  • cuDNN7.0.5
  • AMD Ryzen 1600
  • NVIDIA GTX1070

一. 使用CPU运行

这个比较简单,在Ubuntu下载、编译、运行非常方便,按照YOLO主页给的提示执行就可以看到结果。

1. 下载

git clone https://github.com/pjreddie/darknet

2. 编译

cd darknet
make

执行完命令可以看到生成了可执行文件darknet和库文件libdarknet.a和libdarknet.so。在Ubuntu下使用makefile文件进行编译非常方便,执行一个命即可,比在Windows安装cygwin和GUI的CMake,使用CMake在界面方式编译要简单得多。

3. 下载预训练的权重文件

wget https://pjreddie.com/media/files/yolov3.weights

在Ubuntu中下载速度很慢,要几个小时。我打开了另一台装Windows的电脑,用下载神器迅雷几分钟就下完了,感谢国内下载完这个文件还开着电脑和迅雷的小伙伴们。然后将该文件拷贝至Ubuntu电脑上darknet目录下。

4. 运行检测器

./darknet detect cfg/yolov3.cfg yolov3.weights data/dog.jpg

这里的“./darknet”表示当前目录下的darknet,这点与Windows的命令行不同。运行完可看到下面的结果,并在darknet目录下生成了检测结果的jpg文件。

layer filters size input output
0 conv 32 3 x 3 / 1 416 x 416 x 3 -> 416 x 416 x 32 0.299 BFLOPs
1 conv 64 3 x 3 / 2 416 x 416 x 32 -> 208 x 208 x 64 1.595 BFLOPs
.......
105 conv 255 1 x 1 / 1 52 x 52 x 256 -> 52 x 52 x 255 0.353 BFLOPs
106 detection
truth_thresh: Using default '1.000000'
Loading weights from yolov3.weights...Done!
data/dog.jpg: Predicted in 0.029329 seconds.
dog: 99%
truck: 93%
bicycle: 99%

这几步在我的电脑上执行顺利,没有出现问题。

二. 使用GPU运行

四个步骤相同,区别在于要修改Makefile文件。而且电脑要提前装好CUDA和cuDNN,可参看
环境配置(近期实测)——Ubuntu16.04+CUDA9.0+tensorflow-gpu填坑记。 Makefile文件修改如下:

GPU=0
CUDNN=0
NVCC=nvcc

改为

GPU=1
CUDNN=1
NVCC=/usr/local/cuda-9.0/bin/nvcc #即改为本机CUDA安装地址

修改完执行make提示:

include/darknet.h:16:23: fatal error: cudnn.h: 没有那个文件或目录
compilation terminated.
Makefile:89: recipe for target 'obj/gemm.o' failed
make: *** [obj/gemm.o] Error 1

是因为缺乏头文件cudnn.h,需要将其复制。由于需要管理员权限,我使用命令复制。
进入所在目录~/下载/cuda/include(下载CUDA的默认位置,可能会有不同),运行:

sudo cp cudnn.h /usr/include

然后再darknet目录下执行make,提示

/usr/bin/ld: 找不到 -lcudnn
collect2: error: ld returned 1 exit status
Makefile:83: recipe for target 'libdarknet.so' failed
make: *** [libdarknet.so] Error 1

这是因为缺少库文件libcudnn.so,我使用命令复制。进入所在目录~/下载/cuda/lib64(下载CUDA的默认位置,可能会有不同),打开终端,运行

sudo cp libcudnn.so /usr/local/cuda-9.0/lib64

再在darknet目录下执行make命令,可以看到,已生成可执行程序darkenet和库libdarknet.o和libdarknet.so。剩余过程同上,最后也可以生成有方框标记概率的jpg文件。

Mqtt精髓系列之安全 - 04stone37 - CSDN博客

$
0
0

翻译: https://www.hivemq.com/blog/mqtt-security-fundamentals/

面临的挑战

  在IOT场景中,设备资源受限(计算能力、耗电量等)和网络受限(带宽、稳定性等),这些因素使得高安全性和高可用性更加难以权衡。

安全方案概览

  Mqtt的安全可以在应用层、传输层和网络层进行保证,如下图所示:
这里写图片描述

身份认证

方式一:用户名和密码

   正确:客户端按一定规则生成签名作为密码,然后Broker进行验签;
   错误:用户名和密码直接使用明文;

方式二:TLS/SSL

  客户端使用Broker证书校验Broker身份;Broker使用客户端证书校验客户端证书;

权限校验

  为了限制客户端发布和订阅Topic的权限,Broker必须具备Topic权限管理的功能,而且这些权限可以在运行时动态配置和调整的。对于每个客户端,Topic的权限管理主要包括:

  1. 允许的Topic(明确Topic、带有通配符的Topic);
  2. 允许的操作(发布、订阅和两者);
  3. 允许的Qos等级;

权限校验不通过时的处理:

无Publish权限的处理

  1. Broker直接断开与客户端的连接;
  2. Broker向发布方返回正常的响应,但是不再向订阅方投递消息;

MQTT 3.1.1协议并没有提供一种方式让Broker通知客户端没有Publish权限。

The current MQTT 3.1.1 specification does not define a broker-independent way to inform clients about the unauthorized publish, except disconnecting the client, which may be improved in upcoming MQTT versions.

无Subscribe权限处理

  在SUBACK消息中返回错误码和错误消息即可。

In the case of subscribing to a topic, the broker needs to acknowledge each subscription with a return code. There are 4 different codes for acknowledging each topic with a granted QoS or sending an error code. So if the client has no right to subscribe a specific topic, the broker can notify the client that the subscription was denied.

消息体加密

  采用这种方式时,建议 只针对消息体加密,不要加密消息头中的其它字段(避免Broker进行一次解密)。主要使用场景是 设备资源受限无法采用TLS机制时

场景1:End-to-End (E2E)

这里写图片描述

E2E encryption is broker implementation independent and can be applied to any topic by any MQTT client. If you can’t use authentication and authorization mechanisms or you are using a public broker (like the MQTTDashboard), you can protect your application data from suspicious eyes and MQTT clients.

场景2:Client-to-Broker

这里写图片描述

A Client-to-Broker approach makes sure that the payload of the message is encrypted in the communication between one client and the broker. The broker is able to decrypt the message before distributing the message, so all subscribers receive the original, unencrypted message. This may be a good alternative if you can’t use TLS and want to protect important data from eavesdroppers on the publishing side. (Please read our post about TLS to make sure you understand the risks of not using TLS!) The trusted subscribers are connected via a secure channel (TLS) and thus they are allowed to receive the data in plain text.

数据完整性校验

方式1:使用MACs(推荐)

原因:快速、安全且消耗资源小

  Message Authentication Code Algorithms (like HMAC) are typically very fast compared to digital signatures and provide good security if the shared secret key was exchanged securely prior to the MQTT communication. HMAC calculates the MAC with a cryptographic Hash Function and a cryptographic key. Only senders which know the secret key can create a valid stamp. The disadvantage is, that all clients who are aware of the secret key can sign and verify, since the same key is involved for both processes.
  HMACs work great with MQTT PUBLISH messages and can be used securely even if you don’t have TLS deployed. HMACs are pretty fast to calculate and don’t use much resources on constrained devices.

方式2:使用Checksums (不推荐)

原因:快速但不安全

方式3:使用Digital signatures

原因:极特殊情况使用

  Digital Signatures use public / private key cryptography. The sender signs the message with its private key and the receiver validates the stamp (signature) with the public key of the sending client. Only the private keys can create the signature and thus it’s not possible to fake the signature if an attacker did not obtain the private key.
  As seen in the client certificate blog post, provisioning and revocation of public / private keys is a challenge and adds complexity to the system. Another challenge is, that in Publish / Subscribe Systems like MQTT, the receiver of a message typically is not aware of the identity of the sender, since the communication is decoupled via topics. If it’s guaranteed that only a specific client can publish to a specific topic (e.g. by authorization mechanisms), digital signatures may be a good (and secure!) fit, though.

TLS/SSL补充

TLS对Mqtt性能损

结论:建立连接阶段,TLS很消耗CPU
https://www.hivemq.com/blog/how-does-tls-affect-mqtt-performance/

vue-router+vuex实现加载动态路由和菜单-爱咖啡-51CTO博客

$
0
0

前言

动态路由加载和动态菜单渲染的应用在后端权限控制中十分常见,后端只要加载权限路由进行渲染返回到浏览器就可以。在前后端分离中,权限控制动态路由和动态菜单也是一个非常常见的问题。其实我们最最理想的效果是什么呢?
我们访问一个应用,在登录之前有哪些路由是一定要加载的呢?你看我总结如下,你看下是不是这些:

1.登录路由 (登录功能路由)
2.系统路由(系统消息路由,比如欢迎界面,404,error等的路由)

但是在vue中,一旦实例化,就必须初始化路由,但这个时候你还没有登录,没有获取你的权限路由呀,如果加载全部路由,那么在浏览器上输入路由你就可以访问(这个问题可以使用router.beforeEach钩子进行权限鉴定解决),那么在前后端分离的开发项目中,vue是如何实现动态路由加载实现权限控制的呢?这就是我们这篇文章要写的内容。

我们写过后台渲染都知道怎么去实现,那么放到vue中如何去实现呢?我们先罗列几个问题进行思考,如下

1.vue中路由是如何初始化,放入到vue实例中的?
2.vue中提供了什么实现动态路由加载呢?

我们先顺着这两个问题进行思考,并且顺着这两个问题,我们进行对应方案解决,这个过程中会会出现很多新的问题,我们也针对新问题出对应方案,并且进行优化。

路由初始化

路由初始化发生在什么时候呢?我们可以看主入口文件main.js,下面是我贴出的我的一个项目案例:

import Vue from 'vue'

import 'normalize.css/normalize.css' // A modern alternative to CSS resets

import Element from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'

import '@/styles/index.scss' // global css

import App from './App'
import router from './router'
import store from './store'

import i18n from './lang' // Internationalization
import './icons' // icon
import './errorLog' // error log
import './permission' // permission control
import './mock' // simulation data

import * as filters from './filters' // global filters

Vue.use(Element, {
  size: 'medium', // set element-ui default size
  i18n: (key, value) => i18n.t(key, value)
})

// register global utility filters.
Object.keys(filters).forEach(key => {
  Vue.filter(key, filters[key])
})

Vue.config.productionTip = false

// vue实例化就已经把router初始化了
new Vue({
  el: '#app',
  router,
  store,
  i18n,
  render: h => h(App)
})

通过上面的主入口文件,我们就知道,这个路由初始胡就发生在vue实例化时。这个也很好理解如果你没有初始化路由,那么你就默认只能进入到主窗口,那么接下来主窗口中你没有路由你怎么跳转?程序也不知道你有哪些地方可以跳转呀,路由都是需要先注册到实例中,实例才能定位到相应的视图。从中我们知道, 路由初始化发生在vue实例化时

那么这个时候我们接着我们想要的权限控制目标走:程序一开始,只注册登录路由、系统信息路由(欢迎页面,404路由,error路由),我们称这些为静态路由,登录后我们通过接口获取权限拿到了菜单,这个时候需要进行添加动态路由,把这些菜单信息注册为路由,我们称这些为动态路由。那么vue实例化时,vue-router就已经被初始化,那么我们是不是能够通过类似于往router实例里面添加路由项的方式进行注册路由呢?我们可以查阅文档,也可以查看vue-router源码,有一个叫做addRoutes的方法进行动态注册路由信息,路由对象其实就是一个路由数组,我们通过addRoutes就可以进行动态注册路由,这个跟那个数组中extend功能类似的。

所以说道这里我们知道可以 通过addRoutes进行动态路由注册。好,那么我们就顺着这个思路走下去。

在登录模块中,登录成功后,我们通过api获取后台权限菜单,然后注册路由。代码如下:

// 登录页登录方法
handleLogin() {
      this.$refs.loginForm.validate(valid => {
        if (valid && this.isSuccess) {
          this.loading = true
          this.$store.dispatch('LoginByUsername', this.loginForm).then(() => {
           // 在这个时候进行获取后台权限及菜单
            this.$store.dispatch('getMenus', this.loginForm.name).then((res) => {
             // 把这个菜单信息注册为路由信息
              this.$router.addRoutes(menuitems)
            })
            this.loading = false
           // 除了登录路由、和系统消息路由,这个跟路由是一个欢迎路由,是静态路由
            this.$router.push({ path: '/' })
          }).catch(() => {
            this.$message.error('登陆失败,请检查用户名或密码是否正确')
            this.loading = false
          })
        } else {
          if (!this.isSuccess) {
            this.$message.error('请拉滑动条')
          }
          console.log('error submit!!')
          return false
        }
      })
  }

// 登录方法计算属性
computed: { 
   ...mapGetters([ 
    'menuitems', 
   ]) 
  },

总结一下:
登录成功以后(持久化token),调用获取权限菜单(保存在store里面),这个时候就完成了登录后动态初始化权限菜单的功能。那么这里面所有的路由就是当前用户可访问的菜单,就实现了我们的目标效果。但是呢,store存储权限菜单会有个问题,一旦刷新里面的值就刷掉了,那么这个时候就重新实例化的时候就会跳到404路由中,菜单信息也没有了,那如何解决这个刷新时的问题呢?

我们先分析一下思路:

1.初始化vue实例时,初始化router,包括所有的静态路由。
2.全局钩子检查token是否有效?
        a.如果有效,则通过token获取用户信息保存到store中,根据用户信息获取权限菜单保存到store中,
        动态注册权限菜单的路由信息;
        b.如果token无效,重新定位到静态登录路由进行登录.
3.登录模块中,登录成功后获取用户信息保存到store中,将token保存到store中并持久化到本地,
获取权限菜单保存到store中,动态注册权限菜单的路由信息
4.动态加载完路由后,直接跳到欢迎界面的静态路由
5.一旦页面刷新,那么token就会从store中清除,token失效,那么就会去获得持久化在本地的token
,重新去获取用户信息,权限菜单,重新动态注册路由。
6.token持久化在本地也是有时间限制的,假设token有效期为一周,一旦过了有效期,那么会走2的b情况。

那么上面的思路就是动态加载权限菜单路由信息的简述,整个的环路就通了,刷新问题就解决了。

代码如下:

import router from './router'
import store from './store'
import { Message } from 'element-ui'
import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css'// progress bar style
import { getToken } from '@/utils/auth' // getToken from cookie

NProgress.configure({ showSpinner: false })// NProgress Configuration

// 权限判断
function hasPermission(roles, permissionRoles) {
  if (roles.indexOf('admin') >= 0) return true // admin permission passed directly
  if (!permissionRoles) return true
  return roles.some(role => permissionRoles.indexOf(role) >= 0)
}

const whiteList = ['/login', '/authredirect']// no redirect whitelist

// 全局钩子
router.beforeEach((to, from, next) => {
  NProgress.start() // start progress bar
  // 如果有token
  if (getToken()) { // determine if there has token
    // 登录后进入登录页
    if (to.path === '/login') {
      next({ path: '/' })
      NProgress.done() // if current page is dashboard will not trigger afterEach hook, so manually handle it
    } else {
      // 当进入非登录页时,需要进行权限校验
      if (store.getters.roles.length === 0) { // 判断当前用户是否已拉取完user_info信息
        store.dispatch('GetUserInfo').then(res => { // 拉取user_info
           const roles = res.data.data.roles // note: roles must be a array! such as: ['editor','develop']
           store.dispatch('GenerateRoutes', { roles }).then(() => { // 根据roles权限生成可访问的路由表
             router.addRoutes(store.getters.addRouters) // 动态添加可访问路由表
             next({ ...to, replace: true }) // hack方法 确保addRoutes已完成 ,set the replace: 
           })
        }).catch((err) => {
          store.dispatch('FedLogOut').then(() => {
            Message.error(err || 'Verification failed, please login again')
            next({ path: '/' })
          })
        })
      } else {
        // 没有动态改变权限的需求可直接next() 删除下方权限判断 ↓
        if (hasPermission(store.getters.roles, to.meta.roles)) {
          next()
        } else {
          next({ path: '/401', replace: true, query: { noGoBack: true }})
        }
        // 可删 ↑
      }
    }
  } else {
    /* has no token*/
    if (whiteList.indexOf(to.path) !== -1) { // 在免登录白名单,直接进入
      next()
    } else {
      next('/login') // 否则全部重定向到登录页
      NProgress.done() // if current page is login will not trigger afterEach hook, so manually handle it
    }
  }
})

router.afterEach(() => {
  NProgress.done() // finish progress bar
})

备注:根据模块独立性,我把登录中获取权限列表去掉,都放置在全局钩子中,把上面的代码直接引入到主入口文件main.js中。

另外这里采用vuex进行状态管理,所以从新捋一下思路:

1.vue实例化,初始化静态路由
2.全局钩子进行检查:
    a.token有效
          -如果当前跳转路由是登录路由,直接进入根路由/
            -如果跳转路由非登录路由,则需要进行权限校验,如果用户信息和权限菜单没拉取,
            则进行拉取后将权限菜单动态注册到router中,进行权限判断,如果有用户信息和权限菜单信息,
            则直接进行权限判断。
    b.token无效
          -如果在白名单中,则直接进入
            -进入到登录页

3.全局状态管理采用vuex

到这里我们就已经完成了vue-router+vuex动态注册路由控制权限的方式就说完了,这里我留个思考题给大家:现在根据上面的方式我再引入一个产品实体,(用户 - 产品 - 菜单 ), 用户可以有多个产品权限,每个产品有公用的菜单,也有各产品定制化的菜单,那么这个时候我在前端如果做好权限校验呢?要求:当前用户当前产品的权限菜单才可被访问。

Vuex mapGetters,mapActions - 哈哈呵h - 博客园

$
0
0

一、基本用法

1. 初始化并创建一个项目

1
2
3
vue init webpack-simple vuex-demo
cd vuex-demo
npm install

2. 安装 vuex

1
npminstallvuex -S

3. 在 src 目录下创建 store.js 文件,并在 main.js 文件中导入并配置

store.js 中写入

1
2
3
4
import Vue from'vue'
//引入 vuex 并 use
import Vuex from'vuex'
Vue.use(Vuex)

main.js 文件

1
2
3
4
5
6
7
8
9
10
import Vue from'vue'
import App from'./App.vue'
import store from'./assets/store'//导入 store 对象
 
newVue({
 //配置 store 选项,指定为 store 对象,会自动将 store 对象注入到所有子组件中,在子组件中通过 this.$store 访问该 store 对象
 store,
 el:'#app',
 render: h => h(App)
})

4. 编辑 store.js 文件

在应用 vuex 之前,我们还是需要看懂这个流程图,其实很简单。

vuex

① Vue Components 是我们的 vue 组件,组件会触发(dispatch)一些事件或动作,也就是图中的 Actions;
② 我们在组件中发出的动作,肯定是想获取或者改变数据的,但是在 vuex 中,数据是集中管理的,我们不能直接去更改数据,所以会把这个动作提交(Commit)到 Mutations 中;
③ 然后 Mutations 就去改变(Mutate)State 中的数据;
④ 当 State 中的数据被改变之后,就会重新渲染(Render)到 Vue Components 中去,组件展示更新后的数据,完成一个流程。

Vuex 的核心是 Store(仓库),相当于是一个容器,一个 Store 实例中包含以下属性的方法:

state 定义属性(状态 、数据)

store.js 中写入

1
2
3
4
5
6
7
8
9
10
11
// 定义属性(数据)
varstate = {
 count:6
}
// 创建 store 对象
const store =newVuex.Store({
 state
})
 
// 导出 store 对象
exportdefaultstore;

方式1、 在 app.vue 中就能通过 this.$store 访问该 store 对象 ,获取该 count 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
 <div id="app">
 //把 count 方法直接写入,可自己执行
 <h1>{{count}}</h1>
 </div>
</template>
 
<script>
exportdefault{
 name:'app',
 computed:{
 count(){
  //返回获取到的数据
  returnthis.$store.state.count
 }
 }
}
</script>

方式2、vuex 提供的 mapGetters 和 mapActions 来访问

mapGetters 用来获取属性(数据)

① 在 app.vue 中引入 mapGetters

1
import {mapGetters} from'vuex'

② 在 app.vue 文件的计算属性中调用 mapGetters 辅助方法,并传入一个数组,在数组中指定要获取的属性  count

1
2
3
4
5
6
7
8
9
10
<script>
import {mapGetters,mapActions} from'vuex'
exportdefault{
 name:'app',
 computed:mapGetters([
 //此处的 count 与以下 store.js 文件中 getters 内的 count 相对应
 'count'
 ])
}
</script>

③ 在 store.js 中定义 getters 方法并导出

getters 用来获取属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import Vue from'vue'
import Vuex from'vuex'
Vue.use(Vuex)
 
// 定义属性(数据)
varstate = {
 count:6
}
// 定义 getters
vargetters={
 //需要传个形参,用来获取 state 属性
 count(state){
  returnstate.count
 }
}
// 创建 store 对象
const store =newVuex.Store({
 state,
 getters
})
 
// 导出 store 对象
exportdefaultstore;

这样页面上就会显示传过来的数据了!接下来我们来通过动作改变获取到的数据

④在 store.js 中定义 actions 和 mutations 方法并导出

actions 定义方法(动作),可以使异步的发送请求。

commit 提交变化,修改数据的唯一方式就是显示的提交 mutations

mutations 定义变化,处理状态(数据)的改变

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import Vue from'vue'
import Vuex from'vuex'
Vue.use(Vuex)
 
// 定义属性(数据)
varstate = {
 count:6
}
 
// 定义 getters
vargetters={
 count(state){
  returnstate.count
 }
}
 
// 定义 actions ,要执行的动作,如流程的判断、异步请求
const actions ={
 // ({commit,state}) 这种写法是 es6 中的对象解构
 increment({commit,state}){
  //提交一个名为 increment 的变化,名字可自定义,可以认为是类型名,与下方 mutations 中的 increment 对应
  //commit 提交变化,修改数据的唯一方式就是显式的提交 mutations
  commit('increment')
 }
}
 
// 定义 mutations ,处理状态(数据) 的改变
const mutations ={
 //与上方 commit 中的 ‘increment' 相对应
 increment(state){
  state.count ++;
 }
}
// 创建 store 对象
const store =newVuex.Store({
 state,
 getters,
 actions,
 mutations
})
 
// 导出 store 对象
exportdefaultstore;

⑤ 在 app.vue 中引入 mapActions ,并调用

mapActions 用来获取方法(动作)

1
import {mapGetters,mapActions} from'vuex'

调用 mapActions 辅助方法,并传入一个数组,在数组中指定要获取的方法 increment

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
 <div id="app">
 //这个 increment 方法与下面 methods 中的 increment 相对应
 <button @click="increment">增加</button>
 <button>减少</button>
 <h1>{{count}}</h1>
 </div>
</template>
 
<script>
import {mapGetters,mapActions} from'vuex'
exportdefault{
 name:'app',
 computed:mapGetters([
 'count'
 ]),
 methods:mapActions([
 //该 increment 来自 store.js 中导出的 actions 和 mutations 中的 increment
 'increment',
 ])
}
</script>

这样就能通过 button 来改变获取到的 count 了。

看起来确实是挺绕的,需要在理解了原理的情况下,再细细琢磨,加深理解。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

 

原文链接:https://www.jianshu.com/p/b014ff74bdb6

 

/*getter是state的get方法,没有get页面就获取不到数据

获取页面:
import {mapGetters,mapActions} from 'vuex'<h1>{{count}}</h1>computed:mapGetters(['count'
 ]),

store.js:

var state = {
 count:6
},
var getters={
 count(state){
  return state.count
 }
}

改变数据页面:<button@click="increment">增加</button>methods:mapActions([
 //该 increment 来自 store.js 中导出的 actions 和 mutations 中的 increment 
 'increment',
 ])

先发给action:
const actions ={
 // ({commit,state}) 这种写法是 es6 中的对象解构
 increment({commit,state}){
  //提交一个名为 increment 的变化,名字可自定义,可以认为是类型名,与下方 mutations 中的 increment 对应
  //commit 提交变化,修改数据的唯一方式就是显式的提交 mutations
  commit('increment') 
 }
}
再发给mutations:
const mutations ={
 //与上方 commit 中的 ‘increment' 相对应
 increment(state){
  state.count ++;
 }
}
*/

 

不懂数据库索引的底层原理?那是因为你心里没点b树 - 苏苏喂 - 博客园

$
0
0

本文在个人技术博客不同步发布,详情可 用力戳
亦可扫描屏幕右侧二维码关注个人公众号,公众号内有个人联系方式,等你来撩...

  前几天下班回到家后正在处理一个白天没解决的bug,厕所突然传来对象的声音:
  对象:xx,你有《时间简史》吗?
  我:我去!妹子,你这啥癖好啊,我有时间也不会去捡屎啊!
  对象:...人家说的是霍金的科普著作《时间简史》,是一本书啦!
  我:哦,那我没有...
  对象:人家想看诶,你明天帮我去图书馆借一本吧...
  我:我明天还要改...
  对象:你是不是不爱我了,分手!
  我:我一大早就去~

  第二天一大早我就到了图书馆,刚进门就看到一个索引牌,标识着不同楼层的功能,这样我很快能定位到我要找的目标所在的楼层了。

  

  我到楼上后又看到每排的书架上又对书的分类进行了细分,这样我能更快的定位到我要找的书具体在哪个书架!

  并且每个楼层都有一台查询终端,输入书名就能查到对应的唯一标识“索书号”,类似于P159-49/164这样的一个编码,书架上的书都是按照这个编码进行排序的!有了这个编码再去对应的书架上,很快就能找到对应的书在书架的具体位置了。

  

  不到十分钟,我就从图书馆借好书出来了。

  这么大的图书馆,我为什么能在这么短的时间内找到我要的书?如果这些书是杂乱无章的堆放,或者没有任何标识的放在书架,我还能这么快的找到吗?

  这不禁让我想到了我们开发中用到的数据库,图书馆的书就类似我们数据表中的数据,楼层索引牌、书架分类标识、索书号就类似我们查找数据的索引。

  那我们常用的数据库的索引底层的一个数据结构是什么样的呢?想到这里我又回到图书馆借了一本《数据库从入门到放弃》!

  要了解数据库索引的底层原理,我们就得先了解一种叫树的数据结构,而树中很经典的一种数据结构就是二叉树!所以下面我们就从二叉树到平衡二叉树,再到B-树,最后到B+树来一步一步了解数据库索引底层的原理!

二叉树(Binary Search Trees)

  二叉树是每个结点最多有两个子树的树结构。通常子树被称作“左子树”(left subtree)和“右子树”(right subtree)。二叉树常被用于实现二叉查找树和二叉堆。二叉树有如下特性:

1、每个结点都包含一个元素以及n个子树,这里0≤n≤2。
2、左子树和右子树是有顺序的,次序不能任意颠倒。左子树的值要小于父结点,右子树的值要大于父结点。

  光看概念有点枯燥,假设我们现在有这样一组数[35 27 48 12 29 38 55],顺序的插入到一个数的结构中,步骤如下






  好了,这就是一棵二叉树啦!我们能看到,经通过一系列的插入操作之后,原本无序的一组数已经变成一个有序的结构了,并且这个树满足了上面提到的两个二叉树的特性!

  但是如果同样是上面那一组数,我们自己升序排列后再插入,也就是说按照[12 27 29 35 38 48 55]的顺序插入,会怎么样呢?

  由于是升序插入,新插入的数据总是比已存在的结点数据都要大,所以每次都会往结点的右边插入,最终导致这棵树严重偏科!!!上图就是最坏的情况,也就是一棵树退化为一个线性链表了,这样查找效率自然就低了,完全没有发挥树的优势了呢!
为了较大发挥二叉树的查找效率,让二叉树不再偏科,保持各科平衡,所以有了平衡二叉树!

平衡二叉树 (AVL Trees)

  平衡二叉树是一种特殊的二叉树,所以他也满足前面说到的二叉树的两个特性,同时还有一个特性:

它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。

  大家也看到了前面[35 27 48 12 29 38 55]插入完成后的图,其实就已经是一颗平衡二叉树啦。

  那如果按照[12 27 29 35 38 48 55]的顺序插入一颗平衡二叉树,会怎么样呢?我们看看插入以及平衡的过程:







  这棵树始终满足平衡二叉树的几个特性而保持平衡!这样我们的树也不会退化为线性链表了!我们需要查找一个数的时候就能沿着树根一直往下找,这样的查找效率和二分法查找是一样的呢!

  一颗平衡二叉树能容纳多少的结点呢?这跟树的高度是有关系的,假设树的高度为h,那每一层最多容纳的结点数量为2^(n-1),整棵树最多容纳节点数为2^0+2^1+2^2+...+2^(h-1)。这样计算,100w数据树的高度大概在20左右,那也就是说从有着100w条数据的平衡二叉树中找一个数据,最坏的情况下需要20次查找。如果是内存操作,效率也是很高的!但是我们数据库中的数据基本都是放在磁盘中的,每读取一个二叉树的结点就是一次磁盘IO,这样我们找一条数据如果要经过20次磁盘的IO?那性能就成了一个很大的问题了!那我们是不是可以把这棵树压缩一下,让每一层能够容纳更多的节点呢?虽然我矮,但是我胖啊...

B-Tree

  这颗矮胖的树就是B-Tree,注意中间是杠精的杠而不是减,所以也不要读成B减Tree了~

  那B-Tree有哪些特性呢?一棵m阶的B-Tree有如下特性:

1、每个结点最多m个子结点。
2、除了根结点和叶子结点外,每个结点最少有m/2(向上取整)个子结点。
3、如果根结点不是叶子结点,那根结点至少包含两个子结点。
4、所有的叶子结点都位于同一层。
5、每个结点都包含k个元素(关键字),这里m/2≤k<m,这里m/2向下取整。
6、每个节点中的元素(关键字)从小到大排列。
7、每个元素(关键字)字左结点的值,都小于或等于该元素(关键字)。右结点的值都大于或等于该元素(关键字)。

  是不是感觉跟丈母娘张口问你要彩礼一样,列一堆的条件,而且每一条都让你很懵逼!下面我们以一个[0,1,2,3,4,5,6,7]的数组插入一颗3阶的B-Tree为例,将所有的条件都串起来,你就明白了!







  那么,你是否对B-Tree的几点特性都清晰了呢?在二叉树中,每个结点只有一个元素。但是在B-Tree中,每个结点都可能包含多个元素,并且非叶子结点在元素的左右都有指向子结点的指针。

  如果需要查找一个元素,那流程是怎么样的呢?我们看下图,如果我们要在下面的B-Tree中找到关键字24,那流程如下



  从这个流程我们能看出,B-Tree的查询效率好像也并不比平衡二叉树高。但是查询所经过的结点数量要少很多,也就意味着要少很多次的磁盘IO,这对
性能的提升是很大的。

  前面对B-Tree操作的图我们能看出来,元素就是类似1、2、3这样的数值,但是数据库的数据都是一条条的数据,如果某个数据库以B-Tree的数据结构存储数据,那数据怎么存放的呢?我们看下一张图

  普通的B-Tree的结点中,元素就是一个个的数字。但是上图中,我们把元素部分拆分成了key-data的形式,key就是数据的主键,data就是具体的数据。这样我们在找一条数的时候,就沿着根结点往下找就ok了,效率是比较高的。

B+Tree

  B+Tree是在B-Tree基础上的一种优化,使其更适合实现外存储索引结构。B+Tree与B-Tree的结构很像,但是也有几个自己的特性:

1、所有的非叶子节点只存储关键字信息。
2、所有卫星数据(具体数据)都存在叶子结点中。
3、所有的叶子结点中包含了全部元素的信息。
4、所有叶子节点之间都有一个链指针。

  如果上面B-Tree的图变成B+Tree,那应该如下:

  大家仔细对比于B-Tree的图能发现什么不同?
  1、非叶子结点上已经只有key信息了,满足上面第1点特性!
  2、所有叶子结点下面都有一个data区域,满足上面第2点特性!
  3、非叶子结点的数据在叶子结点上都能找到,如根结点的元素4、8在最底层的叶子结点上也能找到,满足上面第3点特性!
  4、注意图中叶子结点之间的箭头,满足满足上面第4点特性!

B-Tree or B+Tree?

  在讲这两种数据结构在数据库中的选择之前,我们还需要了解的一个知识点是操作系统从磁盘读取数据到内存是以磁盘块(block)为基本单位的, 位于同一个磁盘块中的数据会被一次性读取出来,而不是需要什么取什么。即使只需要一个字节,磁盘也会从这个位置开始,顺序向后读取一定长度的数据放入内存。这样做的理论依据是计算机科学中著名的 局部性原理: 当一个数据被用到时,其附近的数据也通常会马上被使用。
  预读的长度一般为页(page)的整倍数。页是计算机管理存储器的逻辑块,硬件及操作系统往往将主存和磁盘存储区分割为连续的大小相等的块,每个存储块称为一页(在许多操作系统中,页得大小通常为4k)。

  B-Tree和B+Tree该如何选择呢?都有哪些优劣呢?
  1、B-Tree因为非叶子结点也保存具体数据,所以在查找某个关键字的时候找到即可返回。而B+Tree所有的数据都在叶子结点,每次查找都得到叶子结点。所以在同样高度的B-Tree和B+Tree中,B-Tree查找某个关键字的效率更高。
  2、由于B+Tree所有的数据都在叶子结点,并且结点之间有指针连接,在找大于某个关键字或者小于某个关键字的数据的时候,B+Tree只需要找到该关键字然后沿着链表遍历就可以了,而B-Tree还需要遍历该关键字结点的根结点去搜索。
  3、由于B-Tree的每个结点(这里的结点可以理解为一个数据页)都存储主键+实际数据,而B+Tree非叶子结点只存储关键字信息,而每个页的大小有限是有限的,所以同一页能存储的B-Tree的数据会比B+Tree存储的更少。这样同样总量的数据,B-Tree的深度会更大,增大查询时的磁盘I/O次数,进而影响查询效率。
  鉴于以上的比较,所以在常用的关系型数据库中,都是选择B+Tree的数据结构来存储数据!下面我们以mysql的innodb存储引擎为例讲解,其他类似sqlserver、oracle的原理类似!

innodb引擎数据存储

  在InnoDB存储引擎中,也有页的概念,默认每个页的大小为16K,也就是每次读取数据时都是读取4*4k的大小!假设我们现在有一个用户表,我们往里面写数据

  这里需要注意的一点是,在某个页内插入新行时,为了不减少数据的移动,通常是插入到当前行的后面或者是已删除行留下来的空间,所以在 某一个页内的数据并 不是完全有序的(后面页结构部分有细讲),但是为了为了数据访问顺序性,在每个记录中都有一个指向下一条记录的指针,以此构成了一条单向有序链表,不过在这里为了方便演示我是按顺序排列的!

  由于数据还比较少,一个页就能容下,所以只有一个根结点,主键和数据也都是保存在根结点(左边的数字代表主键,右边名字、性别代表具体的数据)。假设我们写入10条数据之后,Page1满了,再写入新的数据会怎么存放呢?我们继续看下图

  有个叫“秦寿生”的朋友来了,但是Page1已经放不下数据了,这时候就需要进行页分裂,产生一个新的Page。在innodb中的流程是怎么样的呢?

1、产生新的Page2,然后将Page1的内容复制到Page2。
2、产生新的Page3,“秦寿生”的数据放入Page3。
3、原来的Page1依然作为根结点,但是变成了一个不存放数据只存放索引的页,并且有两个子结点Page2、Page3。

  这里有两个问题需要注意的是
  1、为什么要复制Page1为Page2而不是创建一个新的页作为根结点,这样就少了一步复制的开销了?
  如果是重新创建根结点,那根结点存储的物理地址可能经常会变,不利于查找。并且在 innodb中根结点是会预读到内存中的,所以结点的物理地址固定会比较好!

  2、原来Page1有10条数据,在插入第11条数据的时候进行裂变,根据前面对B-Tree、B+Tree特性的了解,那这至少是一颗11阶的树,裂变之后每个结点的元素至少为11/2=5个,那是不是应该页裂变之后主键1-5的数据还是在原来的页,主键6-11的数据会放到新的页,根结点存放主键6?
  如果是这样的话新的页空间利用率只有50%,并且会导致更为频繁的页分裂。所以innodb对这一点做了优化,新的数据放入新创建的页,不移动原有页面的任何记录。

  随着数据的不断写入,这棵树也逐渐枝繁叶茂,如下图

  每次新增数据,都是将一个页写满,然后新创建一个页继续写,这里其实是有个隐含条件的,那就是 主键自增!主键自增写入时新插入的数据不会影响到原有页,插入效率高!且页的利用率高!但是如果主键是无序的或者随机的,那每次的插入可能会导致原有页频繁的分裂,影响插入效率!降低页的利用率! 这也是为什么在innodb中建议设置主键自增的原因!

  这棵树的非叶子结点上存的都是主键,那如果一个表没有主键会怎么样?在innodb中,如果一个表没有主键,那默认会找建了唯一索引的列,如果也没有,则会生成一个隐形的字段作为主键!

  有数据插入那就有删除,如果这个用户表频繁的插入和删除,那会导致数据页产生碎片,页的空间利用率低,还会导致树变的“虚高”,降低查询效率!这可以通过 索引重建来消除碎片提高查询效率!

innodb引擎数据查找

  数据插入了怎么查找呢?

1、找到数据所在的页。这个查找过程就跟前面说到的B+Tree的搜索过程是一样的,从根结点开始查找一直到叶子结点。
2、在页内找具体的数据。读取第1步找到的叶子结点数据到内存中,然后通过 分块查找的方法找到具体的数据。

  这跟我们在新华字典中找某个汉字是一样的,先通过字典的索引定位到该汉字拼音所在的页,然后到指定的页找到具体的汉字。innodb中定位到页后用了哪种策略快速查找某个主键呢?这我们就需要从页结构开始了解。

  左边蓝色区域称为Page Directory,这块区域由多个slot组成,是一个稀疏索引结构,即一个槽中可能属于多个记录,最少属于4条记录,最多属于8条记录。槽内的数据是有序存放的,所以当我们寻找一条数据的时候可以先在槽中通过二分法查找到一个大致的位置。

  右边区域为数据区域,每一个数据页中都包含多条行数据。注意看图中最上面和最下面的两条特殊的行记录Infimum和Supremum,这是两个虚拟的行记录。在没有其他用户数据的时候Infimum的下一条记录的指针指向Supremum,当有用户数据的时候,Infimum的下一条记录的指针指向当前页中最小的用户记录,当前页中最大的用户记录的下一条记录的指针指向Supremum,至此整个页内的所有行记录形成一个单向链表。

  行记录被Page Directory逻辑的分成了多个块,块与块之间是有序的,也就是说“4”这个槽指向的数据块内最大的行记录的主键都要比“8”这个槽指向的数据块内最小的行记录的主键要小。但是块内部的行记录不一定有序。

  每个行记录的都有一个n_owned的区域(图中粉红色区域),n_owned标识这个这个块有多少条数据,伪记录Infimum的n_owned值总是1,记录Supremum的n_owned的取值范围为[1,8],其他用户记录n_owned的取值范围[4,8],并且只有每个块中最大的那条记录的n_owned才会有值,其他的用户记录的n_owned为0。

  所以当我们要找主键为6的记录时,先通过 二分法稀疏索引中找到对应的槽,也就是Page Directory中“8”这个槽,“8”这个槽指向的是该数据块中最大的记录,而数据是单向链表结构所以无法逆向查找,所以需要找到上一个槽即“4”这个槽,然后通过“4”这个槽中最大的用户记录的指针沿着链表 顺序查找到目标记录。

聚集索引&非聚集索引

  前面关于数据存储的都是演示的聚集索引的实现,如果上面的用户表需要以“用户名字”建立一个非聚集索引,是怎么实现的呢?我们看下图:

  非聚集索引的存储结构与前面是一样的,不同的是在叶子结点的数据部分存的不再是具体的数据,而数据的聚集索引的key。所以通过非聚集索引查找的过程是先找到该索引key对应的聚集索引的key,然后再拿聚集索引的key到主键索引树上查找对应的数据,这个过程称为 回表

  图中的这些名字均来源于网络,希望没有误伤正在看这篇文章的你~^_^

innodb与MyISAM两种存储引擎对比

  上面包括存储和搜索都是拿的innodb引擎为例,那MyISAM与innodb在存储上有啥不同呢?憋缩话,看图:

  上图为MyISAM主键索引的存储结构,我们能看到的不同是

1、主键索引树的叶子结点的数据区域没有存放实际的数据,存放的是数据记录的地址。
2、数据的存储不是按主键顺序存放的,按写入的顺序存放。

  也就是说innodb引擎数据在物理上是按主键顺序存放,而MyISAM引擎数据在物理上按插入的顺序存放。并且MyISAM的叶子结点不存放数据,所以非聚集索引的存储结构与聚集索引类似,在使用非聚集索引查找数据的时候通过非聚集索引树就能直接找到数据的地址了,不需要 回表,这比innodb的搜索效率会更高呢!

索引优化建议?

  大家经常会在很多的文章或书中能看到一些索引的使用建议,比如说

1、like的模糊查询以%开头,会导致索引失效。
2、一个表建的索引尽量不要超过5个。
3、尽量使用覆盖索引。
4、尽量不要在重复数据多的列上建索引。
5、。。。。。。。。。。。
6、。。。。。。。。。。。

  很多这里就不一一列举了!那看完这篇文章,我们能否带着疑问去分析一下为什么要有这些建议?为什么like的模糊查询以%开头,会导致索引失效?为什么一个表建的索引尽量不要超过5个?为什么? 为什么??为什么???相信看到这里的你再加上自己的一些思考应该有答案了吧?

vue iview-admin 如何兼容IE浏览器(通用) - Gas_station的博客 - CSDN博客

$
0
0

 iview作者说:

对于ie我们没有做过多的兼容处理,如果使用者需要兼容ie,可以install babel-polyfill,然后在main.js上面加载这个包即可


1. 安装babel-polyfill
  `npm install babel-polyfill --save`

2. src/main.js 引入babel-polyfill
  `import '@babel/polyfill`

3. 在根目录增加babel.config.js文件
  ```
  module.exports = {
    presets: [
      ['@vue/app', {
        useBuiltIns: 'entry' // src全局es5编译
      }]
    ]
  }
  ```

4. vue.config.js引用babel-polyfill并且加入配置
  ```
  require('babel-polyfill')
  module.exports = {
    ...{ /* 一些别的配置 */ },
    
    // 关键配置:node_modules里面需要用babel-loader进行编译的包(node_modules的包如果没做 es6 => es5 转码则需要自己用babel-polyfill进行手动处理)
    // 如果IE下还跑不起来,控制台发现有其他es6语法的,尝试调试去找到底是哪个包
    transpileDependencies: [
      'iview','axios'
    ],
  }
  ```

另一种写法如图:

 

 

OK,收工!如果可以实现记得点赞分享,谢谢老铁~


Flex 布局在各家浏览器下的一些bug,尤其是IE - kiera - 博客园

$
0
0
  1. Safari 10 and below uses min/max width/height declarations for actually rendering the size of flex items, but it ignores those values when calculating how many items should be on a single line of a multi-line flex container. Instead, it simply uses the item's flex-basis value, or its width if the flex basis is set to auto.  see bug. Fixed in all versions > 10.
  2. IE 11 requires a unit to be added to the third argument, the flex-basis property  see MSFT documentation
  3. In IE10 and IE11, containers with  display: flex and  flex-direction: column will not properly calculate their flexed childrens' sizes if the container has  min-height but no explicit  height property.  See bug.
  4. Firefox 51 and below does not support  Flexbox in button elements. Fixed in version 52.
  5. IE 11 does not vertically align items correctly when  min-height is used  see bug
  6. Flexbugs: community-curated list of flexbox issues and cross-browser workarounds for them
  7. In IE10 the default value for  flex is  0 0 auto rather than  0 1 auto as defined in the latest spec.
  8. In Safari 10.1 and below, the height of (non flex) children are not recognized in percentages. However other browsers recognize and scale the children based on percentage heights. Fixed in all versions > 10.1 ( See bug) The bug also appeared in Chrome but was fixed in  Chrome 51

 

1. Safari 10 及以下用 min/max width/height 这些声明来渲染弹性伸缩项目的尺寸,但是如果容器包含多行项目,它不会计算有多少项目在同一行上。相反它只简单计算 flex-basis 的值,如果flex-basis 值未设置,默认为auto的话,则使用弹性项目设定的宽度。原文提供了该bug链接 - Safari (WebKit) doesn't wrap element within flex when width comes below min-width,给弹性伸缩项目宽度设定min-width后,在Safari浏览器中不会换行,这个bug在10以上的版本已修复。

2. (该被扫进历史垃圾堆的IE8,9不支持flex。IE10以后支持了,要加-ms-前缀。就算是IE11,也不省心。来自本喵団団的吐槽)

在IE11下,flex: 0 1 auto; 第三个参数要加一个单位。(第三个参数就是flex-basis属性的值。这个值可以是数字,比如长度单位或者百分比,也可以是auto,长度根据内容决定。)

看这里提供的链接, see MSFT documentation

这里提到,微软发布的新浏览器,Microsoft Edge对于flex-baisis的兼容情况:

不再支持 -ms-前缀,

支持flex-basis的值为auto,既长度等于项目的长度,如果未指定长度,根据内容决定。

Windows Phone 8.1 的IE 和 微软Edge,可支持 -webkit-flex-basis 别名。

3. IE10、11-preview:flex容器如果设定了属性flex-direction: column, 以及min-height,但是未设定高度,在计算子项目的尺寸时将出现偏差...这个bug已经在Edge解决。

(微软的意思是,该进垃圾堆的老旧浏览器就赶紧扔掉,鼓励用户更新新的浏览器版本。又不是安全问题,反正宝宝就是不修复IE10的这个跟安全问题无关的bug。)

4. Firefox 51及以下版本不支持按钮元素作为弹性容器,版本52已修复。  Flexbox in button elements. (按钮元素比较特别,它不仅仅不支持flex,还不支持overflow:scroll, display:table等等。)

5. IE 11: Flex容器如果设定了min-height,那么 align-items: center; 不起作用。 see bug

(看bug链接里描述的,设定height:auto; 并设定min-height,  align-items: center不能让容器下的项目垂直居中。)

6. Flexbugs: 这是个gitub地址,精选了一些flex容器问题以及跨浏览器的解决办法。目的是提供flex布局下出现bug的解决方案。

7. IE 10:felx的默认值是 0 0 auto。而最新规范的默认值是 0 1 auto。(第二个参数是flex-shrink,规范默认为1,如果空间不足,该子项目将缩小。而IE默认为0,则默认项目不缩小。)

DeclarationWhat it should meanWhat it means in IE 10
(no flex declaration)flex: 0 1 autoflex: 0 0 auto
flex: 1flex: 1 1 0%flex: 1 0 0px
flex: autoflex: 1 1 autoflex: 1 0 auto
flex: initialflex: 0 1 autoflex: 0 0 auto

8. Safari 10.1 及以下版本:弹性伸缩项目的高度如果设置为百分比,无法被识别。10.1以后的版本,以及在Chrome51版本,这个bug得到修复。

 

下一篇将重点介绍IE浏览器,毕竟IE不奇葩谁奇葩


链接:

https://github.com/philipwalton/flexbugs


理解一下nnvm 和 tvm的区别 - jxr041100 - 博客园

$
0
0

NNVM compiler可以将前端框架中的工作负载直接编译到硬件后端,能在高层图中间表示(IR)中表示和优化普通的深度学习工作负载,也能为不同的硬件后端转换计算图、最小化内存占用、优化数据分布、融合计算模式。

编译器的典型工作流如下图所示:

 

 

这个编译器基于此前发布的TVM堆栈中的两个组件:NNVM用于计算图,TVM用于张量运算。

其中,NNVM的目标是将不同框架的工作负载表示为标准化计算图,然后将这些高级图转换为执行图。

TVM提供了一种独立于硬件的特定域语言,以简化张量索引层次中的运算符实现。另外,TVM还支持多线程、平铺、缓存等。

对框架和硬件的支持

编译器中的NNVM模块,支持下图所示的深度学习框架:

 

 

具体来说,MXNet的计算图能直接转换成NNVM图,对Keras计算图的直接支持也正在开发中。

同时,NNVM compiler还支持其他模型格式,比如说微软和Facebook前不久推出的ONNX,以及苹果CoreML。

通过支持ONNX,NNVM compiler支持Caffe2、PyTorch和CNTK框架;通过支持CoreML,这个编译器支持Caffe和Keras。

而编译器中的TVM模块,目前附带多个编码生成器,支持多种后端硬件,其中包括为X86和ARM架构的CPU生成LLVM IR,为各种GPU输出CUDA、OpenCL和Metal kernel。

 

Kubernetes的负载均衡问题(Nginx Ingress) - ericnie - 博客园

$
0
0

Kubernetes关于服务的暴露主要是通过NodePort方式,通过绑定minion主机的某个端口,然后进行pod的请求转发和负载均衡,但这种方式下缺陷是

  • Service可能有很多个,如果每个都绑定一个node主机端口的话,主机需要开放外围一堆的端口进行服务调用,管理混乱
  • 无法应用很多公司要求的防火墙规则

理想的方式是通过一个外部的负载均衡器,绑定固定的端口,比如80,然后根据域名或者服务名向后面的Service ip转发,Nginx很好的解决了这个需求,但问题是如果有新的服务加入,如何去修改Nginx的配置,并且加载这些配置? Kubernetes给出的方案就是Ingress,Ingress包含了两大主件Ingress Controller和Ingress.

  • Ingress解决的是新的服务加入后,域名和服务的对应问题,基本上是一个ingress的对象,通过yaml进行创建和更新进行加载。
  • Ingress Controller是将Ingress这种变化生成一段Nginx的配置,然后将这个配置通过Kubernetes API写到Nginx的Pod中,然后reload.

 

具体实现如下:

1.生成一个默认的后端,如果遇到解析不到的URL就转发到默认后端页面

复制代码
[root@k8s-master ingress]# cat default-backend.yaml 
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: default-http-backend
  labels:
    k8s-app: default-http-backend
  namespace: kube-system
spec:
  replicas: 1
  template:
    metadata:
      labels:
        k8s-app: default-http-backend
    spec:
      terminationGracePeriodSeconds: 60
      containers:
      - name: default-http-backend
        # Any image is permissable as long as:
        # 1. It serves a 404 page at /
        # 2. It serves 200 on a /healthz endpoint
        image: gcr.io/google_containers/defaultbackend:1.0
        livenessProbe:
          httpGet:
            path: /healthz
            port: 8080
            scheme: HTTP
          initialDelaySeconds: 30
          timeoutSeconds: 5
        ports:
        - containerPort: 8080
        resources:
          limits:
            cpu: 10m
            memory: 20Mi
          requests:
            cpu: 10m
            memory: 20Mi
---
apiVersion: v1
kind: Service
metadata:
  name: default-http-backend
  namespace: kube-system
  labels:
    k8s-app: default-http-backend
spec:
  ports:
  - port: 80
    targetPort: 8080
  selector:
    k8s-app: default-http-backend
复制代码

 

2.部署Ingress Controller

具体文件可以参考官方的

https://github.com/kubernetes/ingress/blob/master/examples/daemonset/nginx/nginx-ingress-daemonset.yaml

这里贴一个我的

复制代码
[root@k8s-master ingress]# cat nginx-ingress-controller.yaml 
apiVersion: v1
kind: ReplicationController
metadata:
  name: nginx-ingress-lb
  labels:
    name: nginx-ingress-lb
  namespace: kube-system
spec:
  replicas: 1
  template:
    metadata:
      labels:
        name: nginx-ingress-lb
      annotations:
        prometheus.io/port: '10254'
        prometheus.io/scrape: 'true'
    spec:
      terminationGracePeriodSeconds: 60
      hostNetwork: true
      containers:
      - image: gcr.io/google_containers/nginx-ingress-controller:0.9.0-beta.7
        name: nginx-ingress-lb
        readinessProbe:
          httpGet:
            path: /healthz
            port: 10254
            scheme: HTTP
        livenessProbe:
          httpGet:
            path: /healthz
            port: 10254
            scheme: HTTP
          initialDelaySeconds: 10
          timeoutSeconds: 1
        ports:
        - containerPort: 80
          hostPort: 80
        - containerPort: 443
          hostPort: 443
        env:
          - name: POD_NAME
            valueFrom:
              fieldRef:
                fieldPath: metadata.name
          - name: POD_NAMESPACE
            valueFrom:
              fieldRef:
                fieldPath: metadata.namespace
          - name: KUBERNETES_MASTER
            value: http://192.168.0.105:8080
        args:
        - /nginx-ingress-controller
        - --default-backend-service=$(POD_NAMESPACE)/default-http-backend
        - --apiserver-host=http://192.168.0.105:8080
复制代码

曾经出现的问题是,启动后pod总是在CrashLoopBack的状态,通过logs一看发现nginx-ingress-controller的启动总是去连接apiserver内部集群ip的443端口,导致因为安全问题不让启动,后来在args里面加入

- --apiserver-host=http://192.168.0.105:8080  

后成功启动.

 

3.配置ingress

 配置如下

复制代码
[root@k8s-master ingress]# cat dashboard-weblogic.yaml 
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: dashboard-weblogic-ingress
  namespace: kube-system
spec:
  rules:
  - host: helloworld.eric
    http:
      paths:
      - path: /console
        backend:
          serviceName: helloworldsvc 
          servicePort: 7001
      - path: /
        backend:
          serviceName: kubernetes-dashboard
          servicePort: 80
复制代码

理解如下:

  • host指虚拟出来的域名,具体地址(我理解应该是Ingress-controller那台Pod所在的主机的地址)应该加入/etc/hosts中,这样所有去helloworld.eric的请求都会发到nginx
  • path:/console匹配后面的应用路径
  • servicePort主要是定义服务的时候的端口,不是NodePort.
  • path:/ 匹配后面dashboard应用的路径,以前通过访问master节点8080/ui进入dashboard的,但dashboard其实是部署在minion节点中,实际是通过某个路由语句转发过去而已,dashboard真实路径如下:

而yaml文件是

复制代码
[root@k8s-master ~]# cat kubernetes-dashboard.yaml 
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
# Keep the name in sync with image version and
# gce/coreos/kube-manifests/addons/dashboard counterparts
  name: kubernetes-dashboard-latest
  namespace: kube-system
spec:
  replicas: 1
  template:
    metadata:
      labels:
        k8s-app: kubernetes-dashboard
        version: latest
        kubernetes.io/cluster-service: "true"
    spec:
      containers:
      - name: kubernetes-dashboard
        image: gcr.io/google_containers/kubernetes-dashboard-amd64:v1.5.1
        resources:
          # keep request = limit to keep this container in guaranteed class
          limits:
            cpu: 100m
            memory: 50Mi
          requests:
            cpu: 100m
            memory: 50Mi
        ports:
        - containerPort: 9090
        args:
         -  --apiserver-host=http://192.168.0.105:8080
        livenessProbe:
          httpGet:
            path: /
            port: 9090
          initialDelaySeconds: 30
          timeoutSeconds: 30
---
kind: Service
metadata:
  name: kubernetes-dashboard
  namespace: kube-system
  labels:
    k8s-app: kubernetes-dashboard
    kubernetes.io/cluster-service: "true"
spec:
  selector:
    k8s-app: kubernetes-dashboard
  ports:
  - port: 80
    targetPort: 9090
复制代码

所以访问192.168.51.5:9090端口就会出现dashboard

 

4.测试 

Ok,一切就绪,装逼开始

访问http://helloworld.eric/console 

 

访问http://helloword.eric/    出现dashboard

 

5.配置TLS SSL访问

TLS的配置相当于WebLogic中证书的配置,配置过程如下

  • 证书生成
复制代码
# 生成 CA 自签证书
mkdir cert && cd cert
openssl genrsa -out ca-key.pem 2048
openssl req -x509 -new -nodes -key ca-key.pem -days 10000 -out ca.pem -subj "/CN=kube-ca"

# 编辑 openssl 配置
cp /etc/pki/tls/openssl.cnf .
vim openssl.cnf

# 主要修改如下
[req]
req_extensions = v3_req # 这行默认注释关着的 把注释删掉
# 下面配置是新增的
[ v3_req ]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
subjectAltName = @alt_names
[alt_names]
DNS.1 = helloworld.eric
#DNS.2 = kibana.mritd.me

# 生成证书
openssl genrsa -out ingress-key.pem 2048
openssl req -new -key ingress-key.pem -out ingress.csr -subj "/CN=helloworld.eric" -config openssl.cnf
openssl x509 -req -in ingress.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out ingress.pem -days 365 -extensions v3_req -extfile openssl.cnf
复制代码

 

需要注意的是DNS需要修改成自己的host名,然后在配置csr证书请求的时候需要将域名或者访问名带入subj,比如

-subj "/CN=helloworld.eric" 
  • 创建secret
kubectl create secret tls ingress-secret --namespace=kube-system --key cert/ingress-key.pem --cert cert/ingress.pem 
  • 修改Ingress文件启用证书
复制代码
[root@k8s-master ingress]# cat tls-weblogic.yaml 
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: dashboard-weblogic-ingress
  namespace: kube-system
spec:
  tls:
  - hosts:
    - helloworld.eric
    secretName: ingress-secret
  rules:
  - host: helloworld.eric
    http:
      paths:
      - path: /console
        backend:
          serviceName: helloworldsvc 
          servicePort: 7001
      - path: /
        backend:
          serviceName: kubernetes-dashboard
          servicePort: 80
复制代码
  •  测试

 然后访问helloworld.eric/console,会自动转到https页面,同时查看证书并加入授信列表,可见

 

访问helloworld.eric

 

Kubernetes Nginx Ingress 教程 - 漠然的博客 | mritd Blog

$
0
0

一、Ingress 介绍

Kubernetes 暴露服务的方式目前只有三种:LoadBlancer Service、NodePort Service、Ingress;前两种估计都应该很熟悉,具体的可以参考下  这篇文章;下面详细的唠一下这个 Ingress

1.1、Ingress 是个什么玩意

可能从大致印象上 Ingress 就是能利用 Nginx、Haproxy 啥的负载均衡器暴露集群内服务的工具;那么问题来了,集群内服务想要暴露出去面临着几个问题:

1.2、Pod 漂移问题

众所周知 Kubernetes 具有强大的副本控制能力,能保证在任意副本(Pod)挂掉时自动从其他机器启动一个新的,还可以动态扩容等,总之一句话,这个 Pod 可能在任何时刻出现在任何节点上,也可能在任何时刻死在任何节点上;那么自然随着 Pod 的创建和销毁,Pod IP 肯定会动态变化;那么如何把这个动态的 Pod IP 暴露出去?这里借助于 Kubernetes 的 Service 机制,Service 可以以标签的形式选定一组带有指定标签的 Pod,并监控和自动负载他们的 Pod IP,那么我们向外暴露只暴露 Service IP 就行了;这就是 NodePort 模式:即在每个节点上开起一个端口,然后转发到内部 Pod IP 上,如下图所示

NodePort

1.3、端口管理问题

采用 NodePort 方式暴露服务面临一个坑爹的问题是,服务一旦多起来,NodePort 在每个节点上开启的端口会及其庞大,而且难以维护;这时候引出的思考问题是 “能不能使用 Nginx 啥的只监听一个端口,比如 80,然后按照域名向后转发?” 这思路很好,简单的实现就是使用 DaemonSet 在每个 node 上监听 80,然后写好规则,因为 Nginx 外面绑定了宿主机 80 端口(就像 NodePort),本身又在集群内,那么向后直接转发到相应 Service IP 就行了,如下图所示

use nginx proxy

1.4、域名分配及动态更新问题

从上面的思路,采用 Nginx 似乎已经解决了问题,但是其实这里面有一个很大缺陷:每次有新服务加入怎么改 Nginx 配置?总不能手动改或者来个 Rolling Update 前端 Nginx Pod 吧?这时候 “伟大而又正直勇敢的” Ingress 登场,如果不算上面的 Nginx,Ingress 只有两大组件:Ingress Controller 和 Ingress

Ingress 这个玩意,简单的理解就是 你原来要改 Nginx 配置,然后配置各种域名对应哪个 Service,现在把这个动作抽象出来,变成一个 Ingress 对象,你可以用 yml 创建,每次不要去改 Nginx 了,直接改 yml 然后创建/更新就行了;那么问题来了:”Nginx 咋整?”

Ingress Controller 这东西就是解决 “Nginx 咋整” 的;Ingress Controoler 通过与 Kubernetes API 交互,动态的去感知集群中 Ingress 规则变化,然后读取他,按照他自己模板生成一段 Nginx 配置,再写到 Nginx Pod 里,最后 reload 一下,工作流程如下图

Ingress

当然在实际应用中,最新版本 Kubernetes 已经将 Nginx 与 Ingress Controller 合并为一个组件,所以 Nginx 无需单独部署,只需要部署 Ingress Controller 即可

二、怼一个 Nginx Ingress

上面啰嗦了那么多,只是为了讲明白 Ingress 的各种理论概念,下面实际部署很简单

2.1、部署默认后端

我们知道 前端的 Nginx 最终要负载到后端 service 上,那么如果访问不存在的域名咋整?官方给出的建议是部署一个 默认后端,对于未知请求全部负载到这个默认后端上;这个后端啥也不干,就是返回 404,部署如下

➜  ~ kubectl create -f default-backend.yaml
deployment "default-http-backend" created
service "default-http-backend" created

这个  default-backend.yaml 文件可以在  官方 Ingress 仓库 找到,由于篇幅限制这里不贴了,仓库位置如下

default-backend

2.2、部署 Ingress Controller

部署完了后端就得把最重要的组件 Nginx+Ingres Controller(官方统一称为 Ingress Controller) 部署上

➜  ~ kubectl create -f nginx-ingress-controller.yaml
daemonset "nginx-ingress-lb" created

注意:官方的 Ingress Controller 有个坑,至少我看了 DaemonSet 方式部署的有这个问题:没有绑定到宿主机 80 端口,也就是说前端 Nginx 没有监听宿主机 80 端口(这还玩个卵啊);所以需要把配置搞下来自己加一下  hostNetwork,截图如下

add hostNetwork

同样配置文件自己找一下,地址  点这里,仓库截图如下

Ingress Controller

当然它支持以 deamonset 的方式部署,这里用的就是(个人喜欢而已),所以你发现我上面截图是 deployment,但是链接给的却是 daemonset,因为我截图截错了…..

2.3、部署 Ingress

这个可就厉害了,这个部署完就能装逼了

daitouzhaungbizhanxianjishu

咳咳,回到正题,从上面可以知道 Ingress 就是个规则,指定哪个域名转发到哪个 Service,所以说首先我们得有个 Service,当然 Service 去哪找这里就不管了;这里默认为已经有了两个可用的 Service,以下以 Dashboard 和 kibana 为例

先写一个 Ingress 文件,语法格式啥的请参考  官方文档,由于我的 Dashboard 和 Kibana 都在 kube-system 这个命名空间,所以要指定 namespace,写之前 Service 分布如下

All Service

vim dashboard-kibana-ingress.yml

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: dashboard-kibana-ingress
  namespace: kube-system
spec:
  rules:
  - host: dashboard.mritd.me
    http:
      paths:
      - backend:
          serviceName: kubernetes-dashboard
          servicePort: 80
  - host: kibana.mritd.me
    http:
      paths:
      - backend:
          serviceName: kibana-logging
          servicePort: 5601

装逼成功截图如下

Dashboard

Kibana

三、部署 Ingress TLS

上面已经搞定了 Ingress,下面就顺便把 TLS 怼上;官方给出的样例很简单,大致步骤就两步:创建一个含有证书的 secret、在 Ingress 开启证书;但是我不得不喷一下,文档就提那么一嘴,大坑一堆,比如多域名配置,还有下面这文档特么的是逗我玩呢?

douniwan

3.1、创建证书

首先第一步当然要有个证书,由于我这个 Ingress 有两个服务域名,所以证书要支持两个域名;生成证书命令如下:

# 生成 CA 自签证书
mkdir cert && cd cert
openssl genrsa -out ca-key.pem 2048
openssl req -x509 -new -nodes -key ca-key.pem -days 10000 -out ca.pem -subj "/CN=kube-ca"

# 编辑 openssl 配置
cp /etc/pki/tls/openssl.cnf .
vim openssl.cnf

# 主要修改如下
[req]
req_extensions = v3_req # 这行默认注释关着的 把注释删掉
# 下面配置是新增的
[ v3_req ]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
subjectAltName = @alt_names
[alt_names]
DNS.1 = dashboard.mritd.me
DNS.2 = kibana.mritd.me

# 生成证书
openssl genrsa -out ingress-key.pem 2048
openssl req -new -key ingress-key.pem -out ingress.csr -subj "/CN=kube-ingress" -config openssl.cnf
openssl x509 -req -in ingress.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out ingress.pem -days 365 -extensions v3_req -extfile openssl.cnf

3.2、创建 secret

创建好证书以后,需要将证书内容放到 secret 中,secret 中全部内容需要 base64 编码,然后注意去掉换行符(变成一行);以下是我的 secret 样例(上一步中 ingress.pem 是证书,ingress-key.pem 是证书的 key)

vim ingress-secret.yml

apiVersion: v1
data:
  tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM5akNDQWQ2Z0F3SUJBZ0lKQU5TR2dNNnYvSVd5TUEwR0NTcUdTSWIzRFFFQkJRVUFNQkl4RURBT0JnTlYKQkFNTUIydDFZbVV0WTJFd0hoY05NVGN3TXpBME1USTBPRFF5V2hjTk1UZ3dNekEwTVRJME9EUXlXakFYTVJVdwpFd1lEVlFRRERBeHJkV0psTFdsdVozSmxjM013Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLCkFvSUJBUUM2dkNZRFhGSFpQOHI5Zk5jZXlkV015VVlELzAwQ2xnS0M2WjNpYWZ0QlRDK005TmcrQzloUjhJUE4KWW00cjZOMkw1MmNkcmZvQnBHZXovQVRIT0NJYUhJdlp1K1ZaTzNMZjcxZEVLR09nV21LMTliSVAzaGpSeDZhWQpIeGhEVWNab3ZzYWY1UWJHRnUydEF4L2doMTFMdXpTZWJkT0Y1dUMrWHBhTGVzWWdQUjhFS0cxS0VoRXBLMDFGCmc4MjhUU1g2TXVnVVZmWHZ1OUJRUXExVWw0Q2VMOXhQdVB5T3lMSktzbzNGOEFNUHFlaS9USWpsQVFSdmRLeFYKVUMzMnBtTHRlUFVBb2thNDRPdElmR3BIOTZybmFsMW0rMXp6YkdTemRFSEFaL2k1ZEZDNXJOaUthRmJnL2NBRwppalhlQ01xeGpzT3JLMEM4MDg4a0tjenJZK0JmQWdNQkFBR2pTakJJTUM0R0ExVWRFUVFuTUNXQ0VtUmhjMmhpCmIyRnlaQzV0Y21sMFpDNXRaWUlQYTJsaVlXNWhMbTF5YVhSa0xtMWxNQWtHQTFVZEV3UUNNQUF3Q3dZRFZSMFAKQkFRREFnWGdNQTBHQ1NxR1NJYjNEUUVCQlFVQUE0SUJBUUNFN1ByRzh6MytyaGJESC8yNGJOeW5OUUNyYVM4NwphODJUUDNxMmsxUUJ1T0doS1pwR1N3SVRhWjNUY0pKMkQ2ZlRxbWJDUzlVeDF2ckYxMWhGTWg4MU9GMkF2MU4vCm5hSU12YlY5cVhYNG16eGNROHNjakVHZ285bnlDSVpuTFM5K2NXejhrOWQ1UHVaejE1TXg4T3g3OWJWVFpkZ0sKaEhCMGJ5UGgvdG9hMkNidnBmWUR4djRBdHlrSVRhSlFzekhnWHZnNXdwSjlySzlxZHd1RHA5T3JTNk03dmNOaQpseWxDTk52T3dNQ0h3emlyc01nQ1FRcVRVamtuNllLWmVsZVY0Mk1yazREVTlVWFFjZ2dEb1FKZEM0aWNwN0sxCkRPTDJURjFVUGN0ODFpNWt4NGYwcUw1aE1sNGhtK1BZRyt2MGIrMjZjOVlud3ROd24xdmMyZVZHCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
  tls.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBdXJ3bUExeFIyVC9LL1h6WEhzblZqTWxHQS85TkFwWUNndW1kNG1uN1FVd3ZqUFRZClBndllVZkNEeldKdUsramRpK2RuSGEzNkFhUm5zL3dFeHpnaUdoeUwyYnZsV1R0eTMrOVhSQ2hqb0ZwaXRmV3kKRDk0WTBjZW1tQjhZUTFIR2FMN0duK1VHeGhidHJRTWY0SWRkUzdzMG5tM1RoZWJndmw2V2kzckdJRDBmQkNodApTaElSS1N0TlJZUE52RTBsK2pMb0ZGWDE3N3ZRVUVLdFZKZUFuaS9jVDdqOGpzaXlTcktOeGZBREQ2bm92MHlJCjVRRUViM1NzVlZBdDlxWmk3WGoxQUtKR3VPRHJTSHhxUi9lcTUycGRadnRjODJ4a3MzUkJ3R2Y0dVhSUXVhelkKaW1oVzRQM0FCb28xM2dqS3NZN0RxeXRBdk5QUEpDbk02MlBnWHdJREFRQUJBb0lCQUJtRmIzaVVISWVocFYraAp1VkQyNnQzVUFHSzVlTS82cXBzenpLVk9NTTNLMk5EZUFkUHhFSDZhYlprYmM4MUNoVTBDc21BbkQvMDdlQVRzClU4YmFrQ2FiY2kydTlYaU5uSFNvcEhlblFYNS8rKys4aGJxUGN6cndtMzg4K0xieXJUaFJvcG5sMWxncWVBOW0KVnV2NzlDOU9oYkdGZHh4YzRxaUNDdmRETDJMbVc2bWhpcFRKQnF3bUZsNUhqeVphdGcyMVJ4WUtKZ003S1p6TAplYWU0bTJDR3R0bmNyUktodklaQWxKVmpyRWoxbmVNa3RHODFTT3QyN0FjeDRlSnozbmcwbjlYSmdMMHcwU05ZCmlwd3I5Uk5PaDkxSGFsQ3JlWVB3bDRwajIva0JIdnozMk9Qb2FOSDRQa2JaeTEzcks1bnFrMHBXdUthOEcyY00KLzY4cnQrRUNnWUVBN1NEeHRzRFFBK2JESGdUbi9iOGJZQ3VhQ2N4TDlObHIxd2tuTG56VVRzRnNkTDByUm1uZAp5bWQ4aU95ME04aUVBL0xKb3dPUGRRY240WFdWdS9XbWV5MzFVR2NIeHYvWlVSUlJuNzgvNmdjZUJSNzZJL2FzClIrNVQ1TEMyRmducVd2MzMvdG0rS0gwc0J4dEM3U2tSK3Y2UndVQk1jYnM3c0dUQlR4NVV2TkVDZ1lFQXlaaUcKbDBKY0dzWHhqd1JPQ0FLZytEMlJWQ3RBVmRHbjVMTmVwZUQ4bFNZZ3krZGxQaCt4VnRiY2JCV0E3WWJ4a1BwSAorZHg2Z0p3UWp1aGN3U25uOU9TcXRrZW04ZmhEZUZ2MkNDbXl4ZlMrc1VtMkxqVzM1NE1EK0FjcWtwc0xMTC9GCkIvK1JmcmhqZW5lRi9BaERLalowczJTNW9BR0xRVFk4aXBtM1ZpOENnWUJrZGVHUnNFd3dhdkpjNUcwNHBsODkKdGhzemJYYjhpNlJSWE5KWnNvN3JzcXgxSkxPUnlFWXJldjVhc0JXRUhyNDNRZ1BFNlR3OHMwUmxFMERWZWJRSApXYWdsWVJEOWNPVXJvWFVYUFpvaFZ0U1VETlNpcWQzQk42b1pKL2hzaTlUYXFlQUgrMDNCcjQ0WWtLY2cvSlplCmhMMVJaeUU3eWJ2MjlpaWprVkVMRVFLQmdRQ2ZQRUVqZlNFdmJLYnZKcUZVSm05clpZWkRpNTVYcXpFSXJyM1cKSEs2bVNPV2k2ZlhJYWxRem1hZW1JQjRrZ0hDUzZYNnMyQUJUVWZLcVR0UGxKK3EyUDJDd2RreGgySTNDcGpEaQpKYjIyS3luczg2SlpRY2t2cndjVmhPT1Z4YTIvL1FIdTNXblpSR0FmUGdXeEcvMmhmRDRWN1R2S0xTNEhwb1dQCm5QZDV0UUtCZ0QvNHZENmsyOGxaNDNmUWpPalhkV0ZTNzdyVFZwcXBXMlFoTDdHY0FuSXk5SDEvUWRaOXYxdVEKNFBSanJseEowdzhUYndCeEp3QUtnSzZmRDBXWmZzTlRLSG01V29kZUNPWi85WW13cmpPSkxEaUU3eFFNWFBzNQorMnpVeUFWVjlCaDI4cThSdnMweHplclQ1clRNQ1NGK0Q5NHVJUmkvL3ZUMGt4d05XdFZxCi0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg==
kind: Secret
metadata:
  name: ingress-secret
  namespace: kube-system
type: Opaque

创建完成后 create 一下就可

➜  ~ kubectl create -f ingress-secret.yml
secret "ingress-secret" created

其实这个配置比如证书转码啥的没必要手动去做,可以直接使用下面的命令创建,这里写这么多只是为了把步骤写清晰

kubectl create secret tls ingress-secret --key cert/ingress-key.pem --cert cert/ingress.pem

3.3、重新部署 Ingress

生成完成后需要在 Ingress 中开启 TLS,Ingress 修改后如下

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: dashboard-kibana-ingress
  namespace: kube-system
spec:
  tls:
  - hosts:
    - dashboard.mritd.me
    - kibana.mritd.me
    secretName: ingress-secret
  rules:
  - host: dashboard.mritd.me
    http:
      paths:
      - backend:
          serviceName: kubernetes-dashboard
          servicePort: 80
  - host: kibana.mritd.me
    http:
      paths:
      - backend:
          serviceName: kibana-logging
          servicePort: 5601

注意:一个 Ingress 只能使用一个 secret(secretName 段只能有一个),也就是说只能用一个证书,更直白的说就是如果你在一个 Ingress 中配置了多个域名,那么使用 TLS 的话必须保证证书支持该 Ingress 下所有域名;并且这个  secretName 一定要放在上面域名列表最后位置,否则会报错  did not find expected key 无法创建;同时上面的  hosts 段下域名必须跟下面的  rules 中完全匹配

更需要注意一点:之所以这里单独开一段就是因为有大坑;Kubernetes Ingress 默认情况下,当你不配置证书时,会默认给你一个 TLS 证书的,也就是说你 Ingress 中配置错了,比如写了2个  secretName、或者  hosts 段中缺了某个域名,那么对于写了多个  secretName 的情况,所有域名全会走默认证书;对于  hosts 缺了某个域名的情况,缺失的域名将会走默认证书,部署时一定要验证一下证书,不能 “有了就行”;更新 Ingress 证书可能需要等一段时间才会生效

最后重新部署一下即可

➜  ~ kubectl delete -f dashboard-kibana-ingress.yml
ingress "dashboard-kibana-ingress" deleted
➜  ~ kubectl create -f dashboard-kibana-ingress.yml
ingress "dashboard-kibana-ingress" created

注意:部署 TLS 后 80 端口会自动重定向到 443,最终访问截图如下

Ingress TLS

Ingress TLS Certificate

历时 5 个小时鼓捣,到此结束

mac地址imsi码imei码在手机定位中的作用_152*****908@sina.cn_新浪博客

$
0
0
一、MAC地址的定义
MAC(Medium/Media Access Control)地址,意译为媒体访问控制,或称为物理地址、硬件地址,用来定义网络设备的位置。在互联网中,每一个网络终端都有一个MAC地址,而每一个网络节点设备都有一个专属于他的IP地址。网卡的物理地址通常是网卡生产厂商烧入网卡的EPROM,它存储的是传输数据时真正赖以标识发出数据的电脑和接收数据的设备的地址。也就是说在网络底层的物理传输过程中,是通过物理地址来识别网络设备的,MAC地址就如同我们身份证上的身份号码一样,是全球唯一的。移动互联网是传统互联网的扩展,同样遵从互联网的OSI模型结构,而智能手机除作为GSM通信网络终端外,也是移动互联网的终端设备,同样具有一个唯一的MAC地址。
mac地址imsi码imei码在手机定位中的作用


一、MAC地址 
固定的MAC地址与变化的IP地址相关联,形成手机移动轨迹。
移动互联网中数据的传送依靠将ip地址映射到MAC地址上来完成。MAC地址作为手机的身份标识符是固定不变的,但与其成对出现的IP地址却随着手机在互联网中所连接的节点不同而变化。在移动网络环境中,手机从一个节点范围移动到另一个节点范围,经纬坐标已知的各个节点的IP地址不断与手机固定不变的MAC地址相关联,就形成了手机在地里坐标系中的移动轨迹
二、imsi码
基站通过广播imsi码与手机取得联系
当手机开机的时候,会将手机和手机卡的一些信息上传给基站,其中包括IMSI码、IMEI码和MAC码。附近的几个基站根据信号的强弱可以判断手机所在位置。因为基站本身是带GPS模块的,所以就可以对手机进行定位。正常情况下基站通过广播IMSI码与手机取得联系,IMSI码就是基站对手机定位身份的标识。但实际上通过基站对手机进行定位并不只靠IMSI码,也依靠手机与基站之间的信号,也就是说这个手机只是开机就可以定位手机的位置。因为手机定时会和基站进行联系,联系信号中就包含手机的标识信息
三、imei码
imei码  手机运营商可以通过imei码分辨用户设备,追踪用户地理位置
IMEI码是手机鉴别的主要依据,由他可以看出是原厂正品手机,还是水改机或翻新机,但他在手机识别和跟踪方面的作用也不容小觑。手机运营商通过IMEI码分辨用户设备,追踪用户地理位置,记录用户拨打电话、发送短信、上网等行为。当手机被盗的时候,如知道IMEI码,可以通过手机运营商进行手机锁定。即:获知手机被盗之后所用的电话号码,终止手机的通话功能,获知手机的方位。公安和安全部门可以根据特定人员手机的IMEI码,无论其更换了什么手机号,通过基站定位后,使用无线电测向车和单兵测向设备定位,精度足够到具体哪个房间

elk-filebeat收集docker容器日志 - devzxd - 博客园

$
0
0

使用docker搭建elk

1、使用docker-compose文件构建elk。文件如下:

version: '3'
services:
  elk:
    image: sebp/elk:640
    ports:
      - "5601:5601"
      - "9200:9200"
      - "5044:5044"
    environment:
      - ES_JAVA_OPTS=-Xms512m -Xmx512m
    volumes:
      - ~dockerdata/elk:/var/lib/elasticsearch

2、执行docker-compose up -d 启动elk。可以使用docker logs 命令查看elk启动日志。启动成功后打开浏览器访问 http://127.0.0.1:5601

filebeat安装与配置

关于filebeat本文也不做过多介绍。只讲解安装与配置。

1、filebeat的docker-composep

version: '3'
services:
  filebeat:
    image: prima/filebeat:6
    #restart: always
    volumes:
      - ./config/filebeat.yml:/filebeat.yml
      - ~/dockerdata/filebeat:/data
      - /var/lib/docker/containers:/var/lib/docker/containers

挂载说明

  • filebeat.yml配置需要在本地有对应文件,稍后会说到
  • filebeat抓取日志进度数据,挂载到本地,防止filebeat容器重启,所有日志重新抓取
  • 因为要收集docker容器的日志,所以要挂在到docker日志存储目录,使它有读取权限

2、filebeat配置文件设置

  • 在docker-compose.yml同级目录新建config文件夹
  • 在config文件下新建filebeat.yml文件,文件内容如下:
filebeat.prospectors:
- type: log
  enabled: true
  paths:
    - /var/lib/docker/containers/*/*.log #需要读取日志的目录#
  json.keys_under_root: true # 因为docker使用的log driver是json-file,因此采集到的日志格式是json格式,设置为true之后,filebeat会将日志进行json_decode处理
  json.add_error_key: true #如果启用此设置,则在出现JSON解组错误或配置中定义了message_key但无法使用的情况下,Filebeat将添加“error.message”和“error.type:json”键。
  json.message_key: log #一个可选的配置设置,用于指定应用行筛选和多行设置的JSON密钥。 如果指定,键必须位于JSON对象的顶层,且与键关联的值必须是字符串,否则不会发生过滤或多行聚合。
  tail_files: true
  # 将error日志合并到一行
  multiline.pattern: '^([0-9]{4}|[0-9]{2})-[0-9]{2}' 
  multiline.negate: true
  multiline.match: after
  multiline.timeout: 10s
#  registry_file: /opt/filebeat/registry
#-------------------------- Elasticsearch output ------------------------------
# 直接输出到elasticsearch,这里的hosts是elk地址,端口号是elasticsearch端口#
output.elasticsearch:
  hosts: ["10.9.70.62:9200"]

#==================== Elasticsearch template setting ==========================

setup.template.name: "filebeat.template.json"
setup.template.fields: "filebeat.template.json"
setup.template.overwrite: true
setup.template.enabled: false

# 过滤掉一些不必要字段#
processors:
- drop_fields:
    fields: ["input_type", "offset", "stream", "beat"]
  • 在config文件下新建filebeat.template.json文件,文件内容如下:
{"mappings": {"_default_": {"_all": {"norms": false
      },"_meta": {"version": "5.1.2"
      },"dynamic_templates": [
        {"strings_as_keyword": {"mapping": {"ignore_above": 1024,"type": "keyword"
            },"match_mapping_type": "string"
          }
        }
      ],"properties": {"@timestamp": {"type": "date"
        },"beat": {"properties": {"hostname": {"ignore_above": 1024,"type": "keyword"
            },"name": {"ignore_above": 1024,"type": "keyword"
            },"version": {"ignore_above": 1024,"type": "keyword"
            }
          }
        },"input_type": {"ignore_above": 1024,"type": "keyword"
        },"message": {"norms": false,"type": "text"
        },"meta": {"properties": {"cloud": {"properties": {"availability_zone": {"ignore_above": 1024,"type": "keyword"
                },"instance_id": {"ignore_above": 1024,"type": "keyword"
                },"machine_type": {"ignore_above": 1024,"type": "keyword"
                },"project_id": {"ignore_above": 1024,"type": "keyword"
                },"provider": {"ignore_above": 1024,"type": "keyword"
                },"region": {"ignore_above": 1024,"type": "keyword"
                }
              }
            }
          }
        },"offset": {"type": "long"
        },"source": {"ignore_above": 1024,"type": "keyword"
        },"tags": {"ignore_above": 1024,"type": "keyword"
        },"type": {"ignore_above": 1024,"type": "keyword"
        }
      }
    }
  },"order": 0,"settings": {"index.refresh_interval": "5s"
  },"template": "filebeat-*"
}
  • 执行docker-compose up -d 启动filebeat。

在需要抓取docker日志的所有主机上按照以上步骤安装运行filebeat即可。到这一步其实就已经可以在elk里面建立索引查抓取到的日志。但是如果docker容器很多的话,没有办法区分日志具体是来自哪个容器,所以为了能够在elk里区分日志来源,需要在具体的docker容器上做一些配置,接着看下面的内容

docker容器设置

可以给具体的docker容器增加labels,并且设置logging。参考以下docker-compose.yml

version: '3'
services:
  db:
    image: mysql:5.7
    # 设置labels
    labels:
      service: db
    # logging设置增加labels.service
    logging:
      options:
        labels: "service"
    ports:
      - "3306:3306"

重新启动应用,然后访问http://127.0.0.1:5601 重新添加索引。查看日志,可以增加过滤条件 attrs.service:db,此时查看到的日志就全部来自db容器。结果如下图所示:
elk过滤结果

参考文章

采集docker-container日志

Beats详解(四)

首发地址

http://www.devzxd.top/2018/10/25/elk-filebeat-dockerlogs.html

使用kibana可视化报表实时监控你的应用程序,从日志中找出问题,解决问题 - 一线码农 - 博客园

$
0
0

   先结果导向,来看我在kibana dashborad中制作的几张监控图。

 

一:先睹为快

dashboard1:监控几个维度的日志,这么点日志量是因为把无用的清理掉了,而且只接入了部分应用。

          <1>  每日日志总数。

          <2>  每日日志错误数,从log4net中level=ERROR抠出来的。

          <3>  每个应用贡献的日志量(按照应用程序分组)

          <4>  今日错误日志时间分布折线图。

          <5>  今日全量日志时间分布折线图。

   

dashboard2:这个主要用来监控某日智能精准触发的短信数和邮件数以及通道占比情况。

 

dashboard3: 某日发送的营销活动概况,一目了然。

 

        

二:采集注意事项

      接下来我们聊聊这个流程中注意的问题。

 

1.  使用fileBeat 清洗掉垃圾日志

      采集端使用的是filebeat,一个应用程序配置一个prospectors探测器。

#=========================== Filebeat prospectors =============================

filebeat.prospectors:

# Each - is a prospector. Most options can be set at the prospector level, so
# you can use different prospectors for various configurations.
# Below are the prospector specific configurations.



################## 1. IntelligentMarketing.Service3  ##################
- 
  enabled: true
  paths:
    D:\Services\channel3\log\*.log
  exclude_lines: ['^----------','重新排队,暂停 100。$']
  fields:
    appname: IntelligentMarketing.Service3
    ipnet: 10.153.204.199
    ippub: 121.41.176.41
  encoding: gbk
  multiline.pattern: ^(\s|[A-Z][a-z]|-)
  multiline.match: after


################## 2. IntelligentMarketing.Service4  ##################
- 
  enabled: true
  paths:
    D:\Services\channel4\log\*.log
  exclude_lines: ['^----------','重新排队,暂停 100。$']
  fields:
    appname: IntelligentMarketing.Service4
    ipnet: 10.153.204.199
    ippub: 121.41.176.41
  encoding: gbk
  multiline.pattern: ^(\s|[A-Z][a-z]|-)
  multiline.match: after

 

《1》 exclude_lines

            这个用来过滤掉我指定的垃圾日志,比如说以 ----------- 开头的 和  “重新排队,暂停100。”结尾的日志,反正正则怎么用,这里就怎么配吧,有一点注意,

尽量不要配置 contain模式的正则,比如: '.*暂未获取到任何mongodb记录*.'   这样会导致filebeat cpu爆高。

 

《2》 fields

        这个用于配置应用程序专属的一些信息,比如我配置了appname,内网ip,外网ip,方便做后期的日志检索,检索出来就是下面这样子。

    

 

《3》 multiline

         有时候应用程序会抛异常,就存在着如何合并多行信息的问题,我这里做的配置就是如果当前行是以‘空格’,‘字母‘ 和 ‘-’开头的,那么就直接合并到上

一行,比如下面这个Mongodb的FindALL异常堆栈。

 

2. logstash解析日志

      主要还是使用 grok 正则,比如下面这条日志,我需要提取出‘date’,‘threadID’,和 “ERROR” 这三个重要信息。

2017-11-13 00:00:36,904 [65] ERROR [xxx.Monitor.Worker:83] Tick [(null)] - 这是一些测试数据。。

   那么就可以使用如下的grok模式。

match => { "message" => "%{TIMESTAMP_ISO8601:logdate} \[%{NUMBER:threadId}\] %{LOGLEVEL:level}"}

   

    上面这段话的意思就是:提取出的时间给logdate,65给threadId,ERROR给level,然后整个内容整体给默认的message字段,下面是完整的logstash.yml。

input {
    beats {
       port => 5044
    }
}

filter {
    grok {
          match => { "message" => "%{TIMESTAMP_ISO8601:logdate} \[%{NUMBER:threadId}\] %{LOGLEVEL:level}"}
    }
    if ([message] =~ "^----------") {
      drop {}
    }
    date {
        match => ["logdate", "yyyy-MM-dd HH:mm:ss,SSS"]
      # target => "@timestamp"
        timezone => "Asia/Shanghai"
    }
    ruby {
       code => "event.timestamp.time.localtime"
    }
}

output {
  stdout {
   codec => rubydebug { }
  }

   elasticsearch {
       hosts => "10.132.166.225"
       index => "log-%{+YYYY.MM.dd}"
   }
}       

 

 

三: kibana制作

 1. 今日全量日志吞吐走势图

 

这个比较简单。因为本质上是一个聚合计算,aggreration按照 Date Histogram聚合即可。

 

2. 今日错误日志走势图

 

  这个相当于在上面那个按时间聚合分组之后,然后在每一个bucket中再做一个 having level=‘ERROR’的筛选即可。

 

3. 今日普通营销活动发送

  这个就是在bucket桶中做了一个  having message like '%【第四步】 leaflet发送成功%'  ,为什么这么写是因为只要发送成功,我都会追加这么一条日志,

所以大概就是这么个样子,性能上大家应该也知道,对吧。

 

4. 今日应用程序日志吞吐量

 

    这个不想上面那三张图按照时间聚合,而是按照appname 聚合就可以了,还记得我在filebeat的fileld中添加的appname字段吗?

 

四:使用nginx给kibana做权限验证

  为了避开x-pack 的复杂性,大家可以使用nginx给kibana做权限验证。

1.  安装 yum install -y httpd-tools。

 

2. 设置用户名和密码:admin abcdefg

htpasswd -bc /data/myapp/nginx/conf/htpasswd.users damin  abcdefg

3. 修改nginx的配置。

server {
        listen       80;
        server_name  localhost;

        #charset koi8-r;
        #access_log  logs/host.access.log  main;

       auth_basic "Restricted Access";
       auth_basic_user_file /data/myapp/nginx/conf/htpasswd.users;      #登录验证
       location / {

           proxy_pass http://10.122.166.225:5601;     #转发到kibana
           proxy_http_version 1.1;
           proxy_set_header Upgrade $http_upgrade;
           proxy_set_header Connection 'upgrade';
           proxy_set_header Host $host;
           proxy_cache_bypass $http_upgrade;
           allow 222.68.71.185;  #允许的IP
           allow 118.133.47.76; #允许的IP
           deny all;
       }

 

4. 重启nginx

[root@localhost conf]# /data/myapp/nginx/sbin/nginx -s reload

 

然后绑定域名到你的ip之后,登陆就会有用户名密码验证了。

 

好了,本篇就说这么多,希望对你有帮助。

 


filebeat使用elasticsearch的pipeline处理日志内容 | 阿小信的博客

$
0
0

以前使用Logstash时,都是通过logstash来对日志内容做过滤解析等操作,现在6.3.0版本中,可以通过filebeat直接写数据到es中,要对日志内容做处理的话设置对应的pipeline就可以。

以gunicorn的access日志内容为例:

[10/Jul/2018:11:13:55 +0800] 10.242.169.166 "-" "Jakarta Commons-HttpClient/3.0" "POST /message/entry HTTP/1.0" 200 <13968> 0.061638

有以上内容的日志,记录请求发生的时间,发起请求的ip,referer,useragent,status_line, status_code, 进程id, 请求执行时间。

在不使用pipeline的情况下,在kibana中可以看到日志内容在message字段中,如果想要获取每个请求的执行时间是拿不到的。

使用pipeline,需要现在es中增加对应的pipeline,可以在kibana的devtools界面的console中写入,然后点击执行即可

PUT _ingest/pipeline/gunicorn-access
{"description" : "my gunicorn access log pipeline","processors": [
        {"grok": {"field": "message","patterns": ["\\[%{HTTPDATE:timestamp}\\] %{IP:remote} \"%{DATA:referer}\" \"%{DATA:ua}\" \"%{DATA:status_line}\" %{NUMBER:status_code:int} <%{NUMBER:process_id:int}> %{NUMBER:use_time:float}"]
            }
        }
    ]
}

processor使用的grok,主要是patterns的编写,es的 默认正则pattern可以直接使用。注意JSON转义符号。

NUMBER类型最好指定是int还是float,不然默认是string,搜索的时候可能会有问题。

在写patterns的时候,可以借助devtools界面的grokdebugger工具检测是否正确。测试无误即可执行put操作。完成后修改filebeat配置

inputs中设置type字段 用于判断

- type: log
  enabled: true
  paths:
      - /path/to/gunicorn-access.log
  fields:
      type: gunicorn-access
  multiline.pattern: ^\[
  multiline.negate: true
  multiline.match: after

es output中添加

pipelines:
  - pipeline: gunicorn-access
    when.equals:
      fields.type: gunicorn-access

重启。

在开启自带的nginx日志监控时,nginx的错误日志时间会比当前时间快8小时,需要对它的pipeline设置时区,设置方法为:

通过 GET _ingest/pipeline/先找到nginx error log的pipeline名字为: filebeat-6.3.0-nginx-error-pipeline

复制他的pipeline配置,在date字段下添加timezone配置

{"date": {"field": "nginx.error.time","target_field": "@timestamp","formats": ["YYYY/MM/dd H:m:s"
      ],"timezone": "Asia/Shanghai"
    }
}

然后将新的完整pipeline put到es中  PUT _ingest/pipeline/filebeat-6.3.0-nginx-error-pipeline。然后重启es才能生效

 

MySQL事务提交过程(一) - 三石雨 - 博客园

$
0
0

MySQL作为一种关系型数据库,已被广泛应用到互联网中的诸多项目中。今天我们来讨论下事务的提交过程。

                                                       MySQL体系结构

 

由于mysql插件式存储架构,导致开启binlog后,事务提交实质是二阶段提交,通过两阶段提交,来保证存储引擎和二进制日志的一致。

本文仅讨论binlog未打卡状态下的提交流程,后续会讨论打开binlog选项后的提交逻辑。

   

测试环境

OS:WIN7

ENGINE:

bin-log:off

DB:

   

测试条件

set autocommit=0;
-- ----------------------------

-- Table structurefor`user`-- ----------------------------DROP TABLE IF EXISTS `user`;

CREATE TABLE `user` (

`id`int(20) NOT NULL,

`account` varchar(20) NOT NULL,

`name` varchar(20) NOT NULL,

PRIMARY KEY (`id`),

KEY `id` (`id`) USING BTREE,

KEY `name` (`name`) USING BTREE

) ENGINE=InnoDB DEFAULT CHARSET=utf8;

   

测试语句

insert into user values(1, 'sanzhang', '张三');
commit;

   

一般常用的DML:Data Manipulation Language 数据操纵语言,对表的数据进行操作,(insert、update、delete )语句 和 DCL:Data Control Language 数据库控制语言

(创建用户、删除用户、授权、取消授权)语句 和 DDL:Data Definition Language 数据库定义语言,对数据库内部的对象进行创建、删除、修改的操语句

,均是使用MySQL提供的公共接口mysql_execute_command,来执行相应的SQL语句。我们来分析下mysql_execute_command接口执行的流程:

mysql_execute_command

{switch(command)

   {caseSQLCOM_INSERT:

                mysql_insert();break;caseSQLCOM_UPDATE:

                mysql_update();break;caseSQLCOM_DELETE:

                mysql_delete();break;

       ......

   }ifthd->is_error()//语句执行错误trans_rollback_stmt(thd);elsetrans_commit_stmt(thd);

}

   

从上述流程中,可以看到执行任何语句,最后都会执行trans_rollback_stmt或者trans_commit_stmt,这两个分别是语句回滚和语句提交。

语句提交,对于非自动模式下,主要有两个作用:

1、释放autoinc锁,这个锁主要用来处理多个事务互斥的获取自增序列。因此,无论最后执行的是语句提交还是语句回滚,该资源都是需要立马释放掉的。

2、标识语句在事务中的位置,方便语句级回滚。执行commit后,可以进入commit流程。

现在看下具体的事务提交流程:

mysql_execute_command

trans_commit_stmt

ha_commit_trans(thd, FALSE);

{

    TC_LOG_DUMMY:ha_commit_low

        ha_commit_low()   

            innobase_commit

            {//获取innodb层对应的事务结构trx=check_trx_exists(thd);if(单个语句,且非自动提交)

                {//释放自增列占用的autoinc锁资源lock_unlock_table_autoinc(trx);//标识sql语句在事务中的位置,方便语句级回滚trx_mark_sql_stat_end(trx);

                }else事务提交

                {

                     innobase_commit_low()

                     {  

                        trx_commit_for_mysql();<span style="color: #ff0000;">trx_commit</span>(trx); 

                     }//确定事务对应的redo日志是否落盘【根据flush_log_at_trx_commit参数,确定redo日志落盘方式】trx_commit_complete_for_mysql(trx);

trx_flush_log_if_needed_low(trx->commit_lsn);

log_write_up_to(lsn);

                }

            }

}
trx_commit

    trx_commit_low

        {

            trx_write_serialisation_history

            {

                trx_undo_update_cleanup//供purge线程处理,清理回滚页}

            trx_commit_in_memory

            {

                lock_trx_release_locks//释放锁资源trx_flush_log_if_needed(lsn)//刷日志trx_roll_savepoints_free//释放savepoints}

        }

 

MySQL是通过WAL方式,来保证数据库事务的一致性和持久性,即ACID特性中的C(consistent)和D(durability)。

WAL(Write-Ahead Logging)是一种实现事务日志的标准方法,具体而言就是:

1、修改记录前,一定要先写日志;

2、事务提交过程中,一定要保证日志先落盘,才能算事务提交完成。

通过WAL方式,在保证事务特性的情况下,可以提高数据库的性能。

   

从上述流程可以看出,提交过程中,主要做了4件事情,

1、清理undo段信息,对于innodb存储引擎的更新操作来说,undo段需要purge,这里的purge主要职能是,真正删除物理记录。在执行delete或update操作时,实际旧记录没有真正删除,只是在记录上打了一个标记,而是在事务提交后,purge线程真正删除,释放物理页空间。因此,提交过程中会将undo信息加入purge列表,供purge线程处理。

2、释放锁资源,mysql通过锁互斥机制保证不同事务不同时操作一条记录,事务执行后才会真正释放所有锁资源,并唤醒等待其锁资源的其他事务;

3、刷redo日志,前面我们说到,mysql实现事务一致性和持久性的机制。通过redo日志落盘操作,保证了即使修改的数据页没有即使更新到磁盘,只要日志是完成了,就能保证数据库的完整性和一致性;

4、清理保存点列表,每个语句实际都会有一个savepoint(保存点),保存点作用是为了可以回滚到事务的任何一个语句执行前的状态,由于事务都已经提交了,所以保存点列表可以被清理了。

   

关于mysql的锁机制,purge原理,redo日志,undo段等内容,其实都是数据库的核心内容。

MySQL 本身不提供事务支持,而是开放了存储引擎接口,由具体的存储引擎来实现,具体来说支持 MySQL 事务的存储引擎就是 InnoDB。

存储引擎实现事务的通用方式是基于 redo log 和 undo log。

简单来说,redo log 记录事务修改后的数据, undo log 记录事务前的原始数据。

所以当一个事务执行时实际发生过程简化描述如下:

  1. 先记录undo/redo log,确保日志刷到磁盘上持久存储。
  2. 更新数据记录,缓存操作并异步刷盘。
  3. 提交事务,在redo log中写入commit记录。

在 MySQL 执行事务过程中如果因故障中断,可以通过 redo log 来重做事务或通过 undo log 来回滚,确保了数据的一致性。

这些都是由事务性存储引擎来完成的,但 binlog 不在事务存储引擎范围内,而是由 MySQL Server 来记录的。

那么就必须保证 binlog 数据和 redo log 之间的一致性,所以开启了 binlog 后实际的事务执行就多了一步,如下:

  1. 先记录undo/redo log,确保日志刷到磁盘上持久存储。
  2. 更新数据记录,缓存操作并异步刷盘。
  3. 将事务日志持久化到binlog。
  4. 提交事务,在redo log中写入commit记录。

这样的话,只要 binlog 没写成功,整个事务是需要回滚的,而 binlog 写成功后即使 MySQL Crash 了都可以恢复事务并完成提交。

要做到这点,就需要把 binlog 和事务关联起来,而只有保证了 binlog 和事务数据的一致性,才能保证主从数据的一致性。

所以 binlog 的写入过程不得不嵌入到纯粹的事务存储引擎执行过程中,并以内部分布式事务(xa 事务)的方式完成两阶段提交。

   

参考

    1、《高性能MySQL》   

MySQL事务提交过程(二) - 三石雨 - 博客园

$
0
0

上一篇文章我们介绍了在关闭binlog的情况下,事务提交的大概流程。之所以关闭binlog,是因为开启binlog后事务提交流程会变成两阶段提交,这里的两阶段提交并不涉及分布式事务,当然mysql把它称之为内部xa事务(Distributed Transactions),与之对应的还有一个外部xa事务。

这里所谓的两阶段提交分别是prepare阶段和commit阶段。

内部xa事务主要是mysql内部为了保证binlog与redo log之间数据的一致性而存在的,这也是由其架构决定的(binlog在mysql层,而redo log 在存储引擎层);

外部xa事务则是指支持多实例分布式事务,这个才算是真正的分布式事务。

既然是xa事务,必然涉及到两阶段提交,对于内部xa而言,同样存在着提交的两个阶段。

下文会结合源码详细解读内部xa的两阶段提交过程,以及各种情况下,mysqld crash后,mysql如何恢复来保证事务的一致性。

   

测试环境

OS:WIN7

ENGINE:

DB:

   

配置文件参数:

log-bin=D:\mysql\log\5-6-21\mysql-bin

binlog_format=ROW

set autocommit=0;

innodb_support_xa=1sync_binlog=1;

innodb_flush_log_at_trx_commit=1;

【innodb_flush_log_at_trx_commit=1,sync_binlog=1

不同的模式区别在于,写文件调用write和落盘fsync调用的频率不同,所导致的后果是mysqld 或 os crash后,不严格的设置可能会丢失事务的更新。

双一模式是最严格的模式,这种设置情况下,单机在任何情况下不会丢失事务更新。】

   

测试条件

set autocommit=0;
-- ----------------------------

-- Table structurefor`user`-- ----------------------------DROP TABLE IF EXISTS `user`;

CREATE TABLE `user` (

`id`int(20) NOT NULL,

`account` varchar(20) NOT NULL,

`name` varchar(20) NOT NULL,

PRIMARY KEY (`id`),

KEY `id` (`id`) USING BTREE,

KEY `name` (`name`) USING BTREE

) ENGINE=InnoDB DEFAULT CHARSET=utf8;

   

测试语句

insert into user values(1, 'sanzhang', '张三');
commit;

   

prepare阶段:

   1.设置undo state=TRX_UNDO_PREPARED;//trx_undo_set_state_at_prepare调用

   2.刷事务更新产生的redo日志;【步骤1产生的redo日志也会刷入】

MYSQL_BIN_LOG::prepare

ha_prepare_low

    {

engine:

binlog_prepare

innobase_xa_prepare

mysql:

trx_prepare_for_mysql

{1.trx_undo_set_state_at_prepare//设置undo段的标记为TRX_UNDO_PREPARED2.设置事务状态为TRX_STATE_PREPARED3.trx_flush_log_if_needed//将产生的redolog刷入磁盘}

     }

     

commit阶段:

   1.将事务产生的binlog写入文件,刷入磁盘;

   2.设置undo页的状态,置为TRX_UNDO_TO_FREE或TRX_UNDO_TO_PURGE; // trx_undo_set_state_at_finish调用

   3.记录事务对应的binlog偏移,写入系统表空间; //trx_sys_update_mysql_binlog_offset调用

MYSQL_BIN_LOG::commit

    ordered_commit

   {1.FLUSH_STAGE

        flush_cache_to_file//刷binlog2.SYNC_STAGE

        sync_binlog_file//Call fsync() to sync the file to disk.3.COMMIT_STAGE

        ha_commit_low

        {

            binlog_commit

            innobase_commit   

                trx_commit(trx) 

                {

                    trx_write_serialisation_history(trx, mtr);//更新binlog位点,设置undo状态trx_commit_in_memory(trx, lsn);//释放锁资源,清理保存点列表,清理回滚段}        

        } 

    }

   

在任何情况下(机器掉电)mysqld crash或者os crash,MySQL仍然能保证数据库的一致性。数据的一致性是如何做到的哪?正是二阶段提交。

我们结合几种场景来分析下二阶段提交是如何做到的:

1.prepare阶段,redo log落盘前,mysqld crash

2.prepare阶段,redo log落盘后,binlog落盘前,mysqld crash

3.commit阶段,binlog落盘后,mysqld crash

对于第一种情况,由于redo没有落盘,毫无疑问,事务的更新肯定没有写入磁盘,数据库的一致性受影响;

对于第二种情况,这时候redo log写入完成,但binlog还未写入,事务处于TRX_STATE_PREPARED状态,这是提交还是回滚呢?

对于第三种情况,此时,redo log和binlog都已经落盘,只是undo状态没有更新,虽然redo log和binlog已经一致了,事务是否应该提交?

   

我们结合mysqld异常重启后的执行逻辑以及关键的源代码。

对于第三种情况,我们可以搜集到未提交事务的binlog event,所以需要提交;

对于第二种情况,由于binlog未写入,需要通过执行回滚操作来保证数据库的一致性。

   

异常重启后,如何判断事务该提交还是回滚

1.读binlog日志,获取崩溃时没有提交的event;  //info->commit_list中含有该元素

2.若存在,则对应的事务要提交;否则需要回滚。

   

判断事务提交或回滚源码如下:

   

上面讨论了两阶段提交的基本流程,以及服务器异常crash后,mysql如何重启恢复保证binlog和数据的一致性。

简而言之,对于异常的xa事务,若binlog已落盘,则事务应该提交;binlog未落盘,则事务就应该回滚。

   

//异常重启后,回滚流程

innobase_rollback_by_xid

rollback_by_xid

trx_rollback_resurrected

    trx_rollback_active

        row_undo

        {//从回滚页获取undo记录//分析undo记录类型if(insert)

                row_undo_inselserow_undo_mod

        }

 

   

//异常重启后,提交流程

commit_by_xid

trx_commit_for_mysql

   

//写binlog接口

handler.cc:binlog_log_row

sql/binlog.cc:commit

mysys/my_sync:my_sync

sql/binlog.cc:sync_binlog_file

handler/ha_innodb.cc:innobase_xa_prepare

   

binlog日志文件是为了解决MySQL主从复制功能而引入的一份新日志文件,它包含了引发数据变更的事件日志集合。

从库请求主库发送 binlog 并通过日志事件还原数据写入从库,所以从库的数据来源为 binlog。

这样 MySQL 主库只需做到 binlog 与本地数据一致就可以保证主从库数据一致(暂且忽略网络传输引发的主从不一致)。

   

参考

   1、《高性能MySQL》

   2、 mysql 事务提交过程

如何选择分布式事务形态(TCC,SAGA,2PC,补偿,基于消息最终一致性等等) - YOYO&# - 博客园

$
0
0

各种形态的分布式事务

分布式事务有多种主流形态,包括:

  • 基于消息实现的分布式事务
  • 基于补偿实现的分布式事务(gts/seata自动补偿的形式)
  • 基于TCC实现的分布式事务
  • 基于SAGA实现的分布式事务
  • 基于2PC实现的分布式事务

之所以有这么多形态,是 因为任何事情都没有银弹,只有最合适当前场景的解决方案

这些形态的原理已经在很多文章中进行了剖析,用“分布式事务”关键字就能搜到对应的文章,本文不再赘述这些形态的原理,并将重点放在如何根据业务选择对应的分布式事务形态上。

何时选择单机事务?

这个相信大家都很清楚,在条件允许的情况下,我们应该尽可能地使用单机事务,因为单机事务里,无需额外协调其他数据源,减少了网络交互时间消耗以及协调时所需的存储IO消耗,在修改等量业务数据的情况下,单机事务将会有更高的性能。

但单机数据库由于 业务逻辑解耦等因素进行了数据库垂直拆分、或者由于单机数据库性能压力等因素进行了数据库水平拆分之后,数据分布于多个数据库,这时若需要对多个数据库的数据进行协调变更,则需要引入分布式事务。

分布式事务的模式有很多种,那究竟要怎么选择适合业务的模式呢?以下我们将从使用场景、性能、开发成本这几个方面进行分析。

何时选择基于消息实现的事务?

基于消息实现的事务适用于分布式事务的提交或回滚只取决于事务发起方的业务需求,其他数据源的数据变更跟随发起方进行的业务场景。

举个例子,假设存在业务规则:某笔订单成功后,为用户加一定的积分。

在这条规则里,管理订单数据源的服务为事务发起方,管理积分数据源的服务为事务跟随者。

从这个过程可以看到,基于消息队列实现的事务存在以下操作:

  • 订单服务创建订单,提交本地事务
  • 订单服务发布一条消息
  • 积分服务收到消息后加积分

我们可以看到它的整体流程是比较简单的,同时业务开发工作量也不大:

  • 编写订单服务里订单创建的逻辑
  • 编写积分服务里增加积分的逻辑

可以看到该事务形态过程简单,性能消耗小,发起方与跟随方之间的流量峰谷可以使用队列填平,同时业务开发工作量也基本与单机事务没有差别,都不需要编写反向的业务逻辑过程。因此基于消息队列实现的事务是我们除了单机事务外最优先考虑使用的形态。

何时选择利用补偿实现的事务?

但是基于消息实现的事务并不能解决所有的业务场景,例如以下场景:某笔订单完成时,同时扣掉用户的现金。

这里事务发起方是管理订单库的服务,但对整个事务是否提交并不能只由订单服务决定,因为还要确保用户有足够的钱,才能完成这笔交易,而这个信息在管理现金的服务里。这里我们可以引入基于补偿实现的事务,其流程如下:

  • 创建订单数据,但暂不提交本地事务
  • 订单服务发送远程调用到现金服务,以扣除对应的金额
  • 上述步骤成功后提交订单库的事务

以上这个是正常成功的流程,异常流程需要回滚的话,将额外发送远程调用到现金服务以加上之前扣掉的金额。

以上流程比基于消息队列实现的事务的流程要复杂,同时开发的工作量也更多:

  • 编写订单服务里创建订单的逻辑
  • 编写现金服务里扣钱的逻辑
  • 编写现金服务里补偿返还的逻辑

可以看到,该事务流程相对于基于消息实现的分布式事务更为复杂,需要额外开发相关的业务回滚方法,也失去了服务间流量削峰填谷的功能。但其仅仅只比基于消息的事务复杂多一点,若不能使用基于消息队列的最终一致性事务,那么可以优先考虑使用基于补偿的事务形态。

阿里GTS/fescar本质上也是这补偿的编程模型,只不过补偿代码自动生成,无需业务干预,同时接管应用数据源,禁止业务修改处于全局事务状态中的记录。因此,其关于读场景的适用性,可参考补偿。但其在写的适用场景由于引入了全局事务时的写锁,其写适用性介于 TCC以及补偿之间 。

何时选择利用TCC实现的事务

然而基于补偿的事务形态也并非能实现所有的需求,如以下场景:某笔订单完成时,同时扣掉用户的现金,但交易未完成,也未被取消时,不能让客户看到钱变少了。

这时我们可以引入TCC,其流程如下:

  • 订单服务创建订单
  • 订单服务发送远程调用到现金服务,冻结客户的现金
  • 提交订单服务数据
  • 订单服务发送远程调用到现金服务,扣除客户冻结的现金

以上是正常完成的流程,若为异常流程,则需要发送远程调用请求到现金服务,撤销冻结的金额。

以上流程比基于补偿实现的事务的流程要复杂,同时开发的工作量也更多:

  • 订单服务编写创建订单的逻辑
  • 现金服务编写冻结现金的逻辑
  • 现金服务编写扣除现金的逻辑
  • 现金服务编写解冻现金的逻辑

TCC实际上是最为复杂的一种情况,其能处理所有的业务场景,但无论出于性能上的考虑,还是开发复杂度上的考虑,都应该尽量避免该类事务。

何时选择利用SAGA实现的事务?

SAGA可以看做一个异步的、利用队列实现的补偿事务。

其适用于无需马上返回业务发起方最终状态的场景,例如:你的请求已提交,请稍后查询或留意通知 之类。

将上述补偿事务的场景用SAGA改写,其流程如下:

  • 订单服务创建最终状态未知的订单记录,并提交事务
  • 现金服务扣除所需的金额,并提交事务
  • 订单服务更新订单状态为成功,并提交事务

以上为成功的流程,若现金服务扣除金额失败,那么,最后一步订单服务将会更新订单状态为失败。

其业务编码工作量比补偿事务多一点,包括以下内容:

  • 订单服务创建初始订单的逻辑
  • 订单服务确认订单成功的逻辑
  • 订单服务确认订单失败的逻辑
  • 现金服务扣除现金的逻辑
  • 现金服务补偿返回现金的逻辑

但其相对于补偿事务形态有性能上的优势,所有的本地子事务执行过程中,都无需等待其调用的子事务执行,减少了加锁的时间,这在事务流程较多较长的业务中性能优势更为明显。同时,其利用队列进行进行通讯,具有削峰填谷的作用。

因此该形式适用于不需要同步返回发起方执行最终结果、可以进行补偿、对性能要求较高、不介意额外编码的业务场景。

但当然SAGA也可以进行稍微改造,变成与TCC类似、可以进行资源预留的形态。

2PC事务

其适用于参与者较少,单个本地事务执行时间较少,并且参与者自身可用性很高的场景,否则,其很可能导致性能下降严重。

并非一种事务形态就能打遍天下

通过分析我们可以发现,并不存在一种事务形态能解决所有的问题,我们需要根据特定的业务场景选择合适的事务形态。甚至于有时需要混合多种事务形态才能更好的完成目标,如 上面提到的 订单、积分、钱包混合的场景:订单的成功与否需要依赖于钱包的余额,但不依赖于积分的多少,因此可以混合基于消息的事务形态以加积分 及 基于补偿的事务形态以确保扣钱成功,从而得到一个性能更好,编码量更少的形态。

然而目前很多框架都专注于某单一方面的事务形态,如TCC单独一个框架,可靠消息单独一个框架,SAGA单独一个框架,他们各自独立,容易导致以下问题:

  • 由于前期只采用了其中一种类型事务的框架,因为工具目前只有锤子,引入其他工具又涉及测试、阅读代码等过程,因此把所有问题都看做钉子,导致性能偏低或者实现不够优雅
  • 由于不同框架管理事务的形态可能不一致,导致不能很好的协调工作,如某一个TCC框架和另一个基于消息的事务框架无法很好融合。

解决方案

为了解决上面提到的问题,EasyTransaction这个基于Spring的分布式事务框架,实现了上述除2PC以外的所有事务形态,并提供了统一的使用接口,完美地解决了以上的问题。其主要特性如下:

  • 一个框架包含多种事务形态,一个框架搞定所有类型的事务
  • 多种事务形态可混合使用
  • 高性能,若不启用框架的幂等功能,对业务数据库的额外消耗仅为写入25字节的一行
  • 可选的框架幂等实现(包括调用次序错乱处理),大幅减轻业务开发工作量
  • 业务代码可实现完全无入侵
  • 支持嵌套事务
  • 无需额外部署协调者,不同APP的服务协调自身发起的事务
  • 分布式事务ID可关联业务ID,业务类型,APPID,便于监控各个业务的分布式事务执行情况

若各位对ET兴趣,可以到 https://github.com/QNJR-GROUP/EasyTransaction查看详细介绍及示例,本文不再深入介绍

总结

不同业务场景应按需引入不同的事务形态,在条件允许的情况下,个人建议按照如下次序选择对应的事务形态:

单机事务》基于消息的事务》基于补偿的事务》TCC事务

因SAGA事务的形态需要配合较为明显的前端业务交互变更,个人建议在单一事务执行过程较长、存在较多子事务,并且无法使用基于消息的事务形态时使用。

MySQL使用ProxySQL实现读写分离-毛虫小臭臭-51CTO博客

$
0
0

1 ProxySQL简介:

ProxySQL是一个高性能的MySQL中间件,拥有强大的规则引擎。
官方文档: https://github.com/sysown/proxysql/wiki/
下载地址: https://github.com/sysown/proxysql/releases/

2 环境:

  • 系统:CentOS7.5
  • ProxySQL版本:proxysql-1.4.8-1-centos7.x86_64.rpm
  • Mysql版本:MySQL 5.7.22
  • ProxySQL主机IP:192.168.1.101
  • Mysql主库IP:192.168.1.102
  • Mysql从库IP:192.168.1.103

3 前提条件:

  • 防火墙和selinux已关闭;
  • Mysql主从同步已经配置完成;

4 安装ProxySQL:

4.1 安装

# 配ProxySQL源
[root@ProxySQL ~]# cat <<EOF | tee /etc/yum.repos.d/proxysql.repo
[proxysql_repo]
name= ProxySQL
baseurl=http://repo.proxysql.com/ProxySQL/proxysql-1.4.x/centos/\$releasever
gpgcheck=1
gpgkey=http://repo.proxysql.com/ProxySQL/repo_pub_key
EOF

# 安装
[root@ProxySQL ~]# yum install proxysql -y
# 记一次安装依赖:
     perl-Compress-Raw-Bzip2  
     perl-Compress-Raw-Zlib  
     perl-DBD-MySQL  
     perl-DBI    
     perl-IO-Compress  
     perl-Net-Daemon  
     perl-PlRPC

#安装生成的文件:
[root@ProxySQL ~]# rpm -ql proxysql
/etc/init.d/proxysql    # 启动脚本

/etc/proxysql.cnf       # 配置文件,仅在第一次(/var/lib/proxysql/proxysql.db文件不存在)启动时有效
                        # 启动后可以在proxysql管理端中通过修改数据库的方式修改配置并生效(官方推荐方式。)
/usr/bin/proxysql      #主程序文件
/usr/share/proxysql/tools/proxysql_galera_checker.sh
/usr/share/proxysql/tools/proxysql_galera_writer.pl

或者直接下载rpm包或源码包:

github
官网

4.2 配置文件说明

[root@ProxySQL ~]# egrep -v "^#|^$" /etc/proxysql.cnf
datadir="/var/lib/proxysql"
admin_variables=
{
    admin_credentials="admin:admin"     # 定义连接管理端口的用户名和密码
    mysql_ifaces="0.0.0.0:6032"       # 定义管理端口6032;用来连接proxysql的管理数据库,修改proxysql服务的设置以及路由策略
}
mysql_variables=
{
    threads=4                       # 定义每个转发端口开启多少个线程
    max_connections=2048
    default_query_delay=0
    default_query_timeout=36000000
    have_compress=true
    poll_timeout=2000
    interfaces="0.0.0.0:6033"               # 定义转发端口6033;用来连接后端的mysql实例,起到代理转发的作用;
    default_schema="information_schema"
    stacksize=1048576
    server_version="5.7.22"                # 设置后端mysql实例的版本号,仅起到comment的作用
    connect_timeout_server=3000
    monitor_username="monitor"
    monitor_password="monitor"
    monitor_history=600000
    monitor_connect_interval=60000
    monitor_ping_interval=10000
    monitor_read_only_interval=1500
    monitor_read_only_timeout=500
    ping_interval_server_msec=120000
    ping_timeout_server=500
    commands_stats=true
    sessions_sort=true
    connect_retries_on_failure=10
}
mysql_servers =
(
)
mysql_users:
(
)
mysql_query_rules:
(
)
scheduler=
(
)
mysql_replication_hostgroups=
(
)
[root@ProxySQL ~]#  sed -i 's#5.5.30#5.7.22#g' /etc/proxysql.cnf      # 把5.5.30改为自己的版本

4.3 启动proxysql:

4.3.1 添加到开机自启动

[root@ProxySQL ~]# chkconfig proxysql on          # 添加到开机自启动,默认已添加
[root@ProxySQL ~]# chkconfig --list |grep proxysql     # 查看是否开机自启动

Note: This output shows SysV services only and does not include native
      systemd services. SysV configuration data might be overridden by native
      systemd configuration.

      If you want to list systemd services use 'systemctl list-unit-files'.
      To see services enabled on particular target use'systemctl list-dependencies [target]'.

proxysql        0:off   1:off   2:on    3:on    4:on    5:on    6:off

4.3.2 启动

默认情况下,rpm安装的ProxySQL只提供了SysV风格的服务脚本/etc/init.d/proxysql。
所以,可通过该脚本管理ProxySQL的启动、停止等功能。

[root@ProxySQL ~]# /etc/init.d/proxysql --help
Usage: ProxySQL {start|stop|status|reload|restart|initial}

# 启动
[root@ProxySQL ~]#  service proxysql start
Starting ProxySQL: DONE!

# 查看
[root@tcloud-113 ~]# service proxysql status
ProxySQL is running (30422).

# 启动后会监听两个端口,默认为6032和6033。6032端口是ProxySQL的管理端口,6033是ProxySQL对外提供服务的端口。
[root@ProxySQL ~]# ss -lntup |grep proxysql 
tcp    LISTEN     0      128                    *:6032                  *:*      users:(("proxysql",1322,20))
tcp    LISTEN     0      128                    *:6033                  *:*      users:(("proxysql",1322,19))
tcp    LISTEN     0      128                    *:6033                  *:*      users:(("proxysql",1322,18))
tcp    LISTEN     0      128                    *:6033                  *:*      users:(("proxysql",1322,17))
tcp    LISTEN     0      128                    *:6033                  *:*      users:(("proxysql",1322,16))
# 可以看到转发端口的6033开启了4个线程,线程数由全局变量"threads"控制,受cpu物理核心数的影响(每个端口下的线程数<=cpu物理核心数)

如果想要通过systemd管理ProxySQL,可在/usr/lib/systemd/system/proxysql.service中写入如下内容:

[root@ProxySQL ~]# vim /usr/lib/systemd/system/proxysql.service
[Unit]
Description=High Performance Advanced Proxy for MySQL
After=network.target

[Service]
Type=simple
User=mysql
Group=mysql
PermissionsStartOnly=true
LimitNOFILE=102400
LimitCORE=1073741824
ExecStartPre=/bin/mkdir -p /var/lib/proxysql
ExecStartPre=/bin/chown mysql:mysql -R /var/lib/proxysql /etc/proxysql.cnf
ExecStart=/usr/bin/proxysql -f
Restart=always

[root@ProxySQL ~]#

一般来说,ProxySQL很少停止或重启,因为绝大多数配置都可以在线修改。

5 配置proxysql

5.1 添加后端连接mysql主从数据库的配置

5.1.1 mysql主库添加proxysql可以增删改查的账号

例如:
user:proxysql;
password:pwproxysql

mysql> GRANT ALL ON *.* TO 'proxysql'@'192.168.1.%' IDENTIFIED BY 'pwproxysql';

5.1.2 登陆proxysql管理端

[root@ProxySQL ~]# yum install mysql -y         # 安装mysql客户端命令;依赖:mysql-libs
[root@ProxySQL ~]# export MYSQL_PS1="(\u@\h:\p) [\d]> "
[root@ProxySQL ~]# mysql -uadmin -padmin -h127.0.0.1 -P6032     # 默认的用户名密码都是 admin。
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 1
Server version: 5.5.30 (ProxySQL Admin Module)

Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

(admin@127.0.0.1:6032) [(none)]> show databases;
+-----+---------------+-------------------------------------+
| seq | name          | file                                |
+-----+---------------+-------------------------------------+
| 0   | main          |                                     |
| 2   | disk          | /var/lib/proxysql/proxysql.db       |
| 3   | stats         |                                     |
| 4   | monitor       |                                     |
| 5   | stats_history | /var/lib/proxysql/proxysql_stats.db |
+-----+---------------+-------------------------------------+
5 rows in set (0.00 sec)

库说明:

  • main 内存配置数据库,表里存放后端db实例、用户验证、路由规则等信息。表名以 runtime 开头的表示proxysql当前运行的配置内容,不能通过dml语句修改,只能修改对应的不以 runtime开头的(在内存)里的表,然后 LOAD 使其生效, SAVE 使其存到硬盘以供下次重启加载。
  • disk 是持久化到硬盘的配置,sqlite数据文件。
  • stats 是proxysql运行抓取的统计信息,包括到后端各命令的执行次数、流量、processlist、查询种类汇总/执行时间等等。
  • monitor 库存储 monitor 模块收集的信息,主要是对后端db的健康/延迟检查。
  • stats_history 统计信息历史库

5.1.3 Proxysql管理端添加后端连接mysql主从数据库的配置

(admin@127.0.0.1:6032) [(none)]> show tables from main;
+--------------------------------------------+
| tables                                     |
+--------------------------------------------+
| global_variables                           |    # ProxySQL的基本配置参数,类似与MySQL
| mysql_collations                           |    # 配置对MySQL字符集的支持
| mysql_group_replication_hostgroups         |    # MGR相关的表,用于实例的读写组自动分配
| mysql_query_rules                          |    # 路由表
| mysql_query_rules_fast_routing             |    # 主从复制相关的表,用于实例的读写组自动分配
| mysql_replication_hostgroups               |    # 存储MySQL实例的信息
| mysql_servers                              |    # 现阶段存储MySQL用户,当然以后有前后端账号分离的设想
| mysql_users                                |    # 存储ProxySQL的信息,用于ProxySQL Cluster同步
| proxysql_servers                           |    # 运行环境的存储校验值
| runtime_checksums_values                   |    # 
| runtime_global_variables                   |    # 
| runtime_mysql_group_replication_hostgroups |    # 
| runtime_mysql_query_rules                  |    # 
| runtime_mysql_query_rules_fast_routing     |    # 
| runtime_mysql_replication_hostgroups       |    # 与上面对应,但是运行环境正在使用的配置
| runtime_mysql_servers                      |    # 
| runtime_mysql_users                        |    # 
| runtime_proxysql_servers                   |    # 
| runtime_scheduler                          |    # 
| scheduler                                  |    # 定时任务表
+--------------------------------------------+
20 rows in set (0.00 sec)

runtime_开头的是运行时的配置,这些是不能修改的。要修改ProxySQL的配置,需要修改了非runtime_表,修改后必须执行LOAD ... TO RUNTIME才能加载到RUNTIME生效,执行save ... to disk才能将配置持久化保存到磁盘。

下面语句中没有先切换到main库也执行成功了,因为ProxySQL内部使用的SQLite3数据库引擎,和MySQL的解析方式是不一样的。即使执行了USE main语句也是无任何效果的,但不会报错。

使用insert语句添加mysql主机到mysql_servers表中,其中:hostgroup_id 1 表示写组,2表示读组。

(admin@127.0.0.1:6032) [(none)]> insert into mysql_servers(hostgroup_id,hostname,port,weight,comment) values(1,'192.168.1.102',3306,1,'Write Group');
Query OK, 1 row affected (0.00 sec)

(admin@127.0.0.1:6032) [(none)]> insert into mysql_servers(hostgroup_id,hostname,port,weight,comment) values(2,'192.168.1.103',3306,1,'Read Group');
Query OK, 1 row affected (0.00 sec)

(admin@127.0.0.1:6032) [(none)]> select * from mysql_servers;
+--------------+---------------+------+--------+--------+-------------+-----------------+---------------------+---------+----------------+-------------+
| hostgroup_id | hostname      | port | status | weight | compression | max_connections | max_replication_lag | use_ssl | max_latency_ms | comment     |
+--------------+---------------+------+--------+--------+-------------+-----------------+---------------------+---------+----------------+-------------+
| 1            | 192.168.1.102 | 3306 | ONLINE | 1      | 0           | 1000            | 0                   | 0       | 0              | Write Group |
| 2            | 192.168.1.103 | 3306 | ONLINE | 1      | 0           | 1000            | 0                   | 0       | 0              | Read Group  |
+--------------+---------------+------+--------+--------+-------------+-----------------+---------------------+---------+----------------+-------------+
2 rows in set (0.00 sec)

修改后,加载到RUNTIME,并保存到disk。

(admin@127.0.0.1:6032) [(none)]> load mysql servers to runtime;
(admin@127.0.0.1:6032) [(none)]> save mysql servers to disk;

在proxysql主机的mysql_users表中添加刚才创建的账号,proxysql客户端需要使用这个账号来访问数据库。
default_hostgroup默认组设置为写组,也就是1;
当读写分离的路由规则不符合时,会访问默认组的数据库;

(admin@127.0.0.1:6032) [(none)]> insert into mysql_users(username,password,default_hostgroup,transaction_persistent)values('proxysql','pwproxysql',1,1);
Query OK, 1 row affected (0.00 sec)

(admin@127.0.0.1:6032) [(none)]> select * from mysql_users \G
*************************** 1. row ***************************
              username: proxysql        # 后端mysql实例的用户名
             password: pwproxysql       # 后端mysql实例的密码
                 active: 1              # active=1表示用户生效,0表示不生效
                use_ssl: 0
       default_hostgroup: 1             # 用户默认登录到哪个hostgroup_id下的实例
          default_schema: NULL          # 用户默认登录后端mysql实例时连接的数据库,这个地方为NULL的话,则由全局变量mysql-default_schema决定,默认是information_schema
           schema_locked: 0
 transaction_persistent: 1              # 如果设置为1,连接上ProxySQL的会话后,如果在一个hostgroup上开启了事务,那么后续的sql都继续维持在这个hostgroup上,不伦是否会匹配上其它路由规则,直到事务结束。虽然默认是0
               fast_forward: 0              # 忽略查询重写/缓存层,直接把这个用户的请求透传到后端DB。相当于只用它的连接池功能,一般不用,路由规则 .* 就行了
                     backend: 1
                     frontend: 1
          max_connections: 10000            # #该用户允许的最大连接数
1 row in set (0.00 sec)

修改后,加载到RUNTIME,并保存到disk。

(admin@127.0.0.1:6032) [(none)]> load mysql users to runtime;
(admin@127.0.0.1:6032) [(none)]> save mysql users to disk;

5.2 添加健康监测的账号

5.2.1 mysql端添加proxysql只能查的账号

首先在后端master节点上创建一个用于监控的用户名(只需在master上创建即可,因为会复制到slave上),这个用户名只需具有USAGE权限即可。如果还需要监控复制结构中slave是否严重延迟于master(先混个眼熟:这个俗语叫做"拖后腿",术语叫做"replication lag"),则还需具备replication client权限。这里直接赋予这个权限。

mysql> GRANT replication client ON *.* TO 'monitor'@'192.168.1.%' IDENTIFIED BY 'monitor';

5.2.2 proxysql端修改变量设置健康检测的账号

(admin@127.0.0.1:6032) [(none)]> set mysql-monitor_username='monitor';
    Query OK, 1 row affected (0.00 sec)

    (admin@127.0.0.1:6032) [(none)]> set mysql-monitor_password='monitor';
    Query OK, 1 row affected (0.00 sec)

以上设置实际上是在修改global_variables表,它和下面两个语句是等价的:

(admin@127.0.0.1:6032) [(none)]> UPDATE global_variables SET variable_value='monitor'  WHERE variable_name='mysql-monitor_username';
    Query OK, 1 row affected (0.00 sec)

    (admin@127.0.0.1:6032) [(none)]> UPDATE global_variables SET variable_value='monitor'  WHERE variable_name='mysql-monitor_password';
    Query OK, 1 row affected (0.00 sec)

修改后,加载到RUNTIME,并保存到disk。

(admin@127.0.0.1:6032) [(none)]> load mysql variables to runtime;
(admin@127.0.0.1:6032) [(none)]> save mysql variables to disk;

5.3 添加读写分离的路由规则:

  • 将select语句全部路由至hostgroup_id=2的组(也就是读组)
  • 但是select * from tb for update这样的语句是修改数据的,所以需要单独定义,将它路由至hostgroup_id=1的组(也就是写组)
  • 其他没有被规则匹配到的组将会被路由至用户默认的组(mysql_users表中的default_hostgroup)
(admin@127.0.0.1:6032) [(none)]> insert into mysql_query_rules(rule_id,active,match_digest,destination_hostgroup,apply)values(1,1,'^SELECT.*FOR UPDATE$',1,1);
Query OK, 1 row affected (0.00 sec)

(admin@127.0.0.1:6032) [(none)]> insert into mysql_query_rules(rule_id,active,match_digest,destination_hostgroup,apply)values(2,1,'^SELECT',2,1);
Query OK, 1 row affected (0.00 sec)

(admin@127.0.0.1:6032) [(none)]> select rule_id,active,match_digest,destination_hostgroup,apply from mysql_query_rules;
+---------+--------+----------------------+-----------------------+-------+
| rule_id | active | match_digest         | destination_hostgroup | apply |
+---------+--------+----------------------+-----------------------+-------+
| 1       | 1      | ^SELECT.*FOR UPDATE$ | 1                     | 1     |
| 2       | 1      | ^SELECT              | 2                     | 1     |
+---------+--------+----------------------+-----------------------+-------+
2 rows in set (0.00 sec)

5.4 将刚才我们修改的数据加载至RUNTIME中(参考ProxySQL的多层配置结构):

5.4.1 load进runtime,使配置生效

(admin@127.0.0.1:6032) [(none)]> load mysql query rules to runtime;
(admin@127.0.0.1:6032) [(none)]> load admin variables to runtime;

5.4.2 save到磁盘(/var/lib/proxysql/proxysql.db)中,永久保存配置

(admin@127.0.0.1:6032) [(none)]> save mysql query rules to disk;
(admin@127.0.0.1:6032) [(none)]> save admin variables to disk;

6 测试读写分离

6.1 连接proxysql客户端:

登录用户是刚才我们在mysql_user表中创建的用户,端口为6033

[root@centos7 ~]#mysql -uproxysql -ppwproxysql -h127.0.0.1 -P6033
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MySQL connection id is 4
Server version: 5.5.30 (ProxySQL)

Copyright (c) 2000, 2017, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MySQL [(none)]> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| test               |
+--------------------+
4 rows in set (0.00 sec)

MySQL [(none)]>

6.2 尝试修改数据库和查询:

创建两个数据库和查个表。

MySQL [(none)]> create database bigboss;
Query OK, 1 row affected (0.01 sec)

MySQL [(none)]> create database weijinyun;
Query OK, 1 row affected (0.00 sec)

MySQL [(none)]> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| bigboss            |
| mysql              |
| performance_schema |
| test               |
| weijinyun          |
+--------------------+
6 rows in set (0.01 sec)

MySQL [(none)]> select user,host from mysql.user;
+-------------+---------------+
| user        | host          |
+-------------+---------------+
| root        | 127.0.0.1     |
| monitor     | 192.168.1.%  |
| proxysql    | 192.168.1.%  |
| repliaction | 192.168.1.%  |
| root        | ::1           |
|             | centos7       |
| root        | centos7       |
|             | localhost     |
| root        | localhost     |
+-------------+---------------+
9 rows in set (0.01 sec)

6.3 验证读写分离是否成功:

  • proxysql有个类似审计的功能,可以查看各类SQL的执行情况。在proxysql管理端执行:
  • 从下面的hostgroup和digest_text值来看,所有的写操作都被路由至1组,读操作都被路由至2组,
  • 其中1组为写组,2组为读组!
(admin@127.0.0.1:6032) [(none)]> select * from stats_mysql_query_digest;
+-----------+--------------------+----------+--------------------+----------------------------------------+------------+------------+------------+----------+----------+----------+
| hostgroup | schemaname         | username | digest             | digest_text                            | count_star | first_seen | last_seen  | sum_time | min_time| max_time |
+-----------+--------------------+----------+--------------------+----------------------------------------+------------+------------+------------+----------+----------+----------+
| 2         | information_schema | proxysql | 0x3EA85877510AC608 | select * from stats_mysql_query_digest | 2          | 1527233735 | 1527233782 | 4092     | 792| 3300     |
| 1         | information_schema | proxysql | 0x594F2C744B698066 | select USER()                          | 1          | 1527233378 | 1527233378 | 0        | 0| 0        |
| 1         | information_schema | proxysql | 0x02033E45904D3DF0 | show databases                         | 2          | 1527233202 | 1527233495 | 5950     | 1974| 3976     |
| 1         | information_schema | proxysql | 0x226CD90D52A2BA0B | select @@version_comment limit ?       | 2          | 1527233196 | 1527233378 | 0        | 0| 0        |
+-----------+--------------------+----------+--------------------+----------------------------------------+------------+------------+------------+----------+----------+----------+
4 rows in set (0.00 sec)

(admin@127.0.0.1:6032) [(none)]>
读写分离成功!!!

7 参考:

https://blog.51cto.com/bigboss/2103290
http://www.cnblogs.com/f-ck-need-u/p/7586194.html#middleware
http://seanlook.com/2017/04/10/mysql-proxysql-install-config/

Viewing all 532 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>