本系列教程主要是为了弄清楚容器化的原理,纸上得来终觉浅,绝知此事要躬行,理论始终不及动手实践来的深刻,所以这个系列会用go语言实现一个类似docker的容器化功能,最终能够容器化的运行一个进程。
本章的源码已经上传到github,地址如下:
http://github.com/HobbyBear/tinydocker/tree/chapter4
前文我们已经为容器替换了新的根文件系统,但是由于我们启动容器的时候是在一个新的网络命名空间,目前的容器还不能访问外部网络,我们需要在这一节,让容器能够访问外部网络,并且能够实现同一个主机上的容器能够网络互通。
在正式开始编码之前,我将基于最简单的情况,则同一个主机上的容器能够通过ip互相访问的情况,简单的介绍下,容器网络互联的原理,我们是在一个新的网络命名空间 启动的子进程,不同网络命名空间拥有自己的防火墙,路由表,网络设备,所以需要对新生成的网络命名空间进行配置。让网络命名空间内部的网络包能够从网络命名空间内部出去到达主机上。
在linux上,可以用veth虚拟网络设备去连接两个不同网络命名空间,veth设备是成队出现,分别连接到不同的命名空间中, 从veth设备一端进入的网络包能够到达veth设备的另一端, 但在配置容器网络时并不是将veth设备直接连接在另一端的网络命名空间内,因为如果主机上容器过多的话,采用直接两两相连的方式,将会让网络拓扑过于复杂。所以一般是将veth设备连接到一个叫做网桥bridge的虚拟网络设备上,通过它对网络包进行转发。
关于veth设备和bridge的原理和使用,我之前出过一期视频讲解,可以去哪里深入的学习下:
容器网络原理之包流转路径
之前也出过许多对容器网络讲解的系列视频,如果有对容器网络不熟悉的同学,请看这里:
k8s容器互联 flannel host-gw 原理
k8s容器互联 flannel vxlan 模式原理
容器系列 笔记分享
现在,来让我们实现下关于容器网络配置的逻辑,首先容器在创建的时候,得先为它分配一个ip地址,本质上就是为它内部的veth设备分配一个ip地址。这就涉及到如何分配ip地址的问题,这里有两个问题需要解决:
1,当知道一个网络的网段后,如何知道网段内部哪个ip进行了分配,哪个ip没有进行分配。
2,如果知道了这个网段内某个ip没有被分配,如何根据偏移量计算最终没有被分配的ip,比如我知道第8个ip没有被分配,网络网段为192.168.0.0/24 ,那么第8个ip是多少?
首先来看下ip存储的问题,也就是看哪些ip进行了分配,哪些没有进行分配。
这是一个看某个值是否存在的问题,可以通过bitmap去存储,这在快速判断ip是否存在的前提下,也能极大的降低存储成本。
如下所示,如果第一个字节的第1位和第3位被置为1了,说明在这个网段内,第一个ip和第3个ip都被占用了。
一个byte是8个bit,也就可以表示8个ip是否被占用,而一个网段中的ip个数=2的N次方个,其中N=32-网段位数。
用一个实际的例子举例,比如子网掩码是255.255.0.0,说明网段是前面的16位,那么ip个数就是由后16位bit数表示,排除掉其中主机号全为0的网络号和主机号全为1的广播号,可用ip数=2的16次方-2 ,要表示那么多的ip数就需要 (2的16次方-2)/8 大小的字节 约等于8kb,转换成字节数组长度就是8192。
具体实现如下:
一个bitmap用一个byte数组表示
type bitMap struct {
Bitmap []byte
}
bitmap的方法也就3个,
1, 查看第n个ip是不是被分配。
func (b *bitMap) BitExist(pos int) bool {
aIndex := arrIndex(pos)
bIndex := bytePos(pos)
return 1 == 1&(b.Bitmap[aIndex]>>bIndex)
}
arrIndex和bytePos 方法实现如下:
func arrIndex(pos int) int {
return pos / 8
}
func bytePos(pos int) int {
return pos % 8
}
我们最终是要找到这地n个ip所在的bit位,然后查找该bit位是否被置为1,置为1就代表这第n个ip是被分配了。用n/8得到的就是第n个ip所在bit位的字节数组的索引,用n%8得到的余数就是在字节里的第几个bit位,如何取出对应的bit位呢?
首先是b.Bitmap[aIndex]得到对应的字节,然后将该字节右移对应的bit位数,这样第n个ip的bit位就变到了第一个bit位上。整个过程像下面这样:
与运算是双1结果才是1,所以如果最后一个bit位是1则最后与运算的结果就是数字1,如果最后一位bit位是0,则最后运算的结果就是0。
2,设置第n个ip被分配。
设置第n个ip被分配,即设置它对应的bit位为1,首先还是要找到这第n个ip在数组中的位置,然后取出对应字节byte,通过位运算设置其对应的bit位。
func (b *bitMap) BitSet(pos int) {
aIndex := arrIndex(pos)
bIndex := bytePos(pos)
b.Bitmap[aIndex] = b.Bitmap[aIndex] | (1 << bIndex)
}
零bit的或位运算不会改变原bit位值大小,而1的bit的或位运算会将原来bit位置为1,利用这个特性便可以很容易的写出来上面的代码。
整个过程如图所示:
3,释放第n个ip的分配记录。
释放第n个ip原理和前面类似,设置第n个ip对应的bit位为0。
func (b *bitMap) BitClean(pos int) {
aIndex := arrIndex(pos)
bIndex := bytePos(pos)
b.Bitmap[aIndex] = b.Bitmap[aIndex] & (^(1 << bIndex))
}
零bit的与位运算会让原bit位置为0,而1的与位运算不会改变原bit位的值,知道了这个特性再看上述代码应该就很容易了,其中^ 运算符为取反的意思,这样00000100 就会变为 11111011,这样与原bit位进行与位运算就能将索引为2的bit位置为0了。
通过上述bitmap的实现可以解决ip分配的存储问题,但还有一个问题要解决,那就是目前只知道了第几个ip没有分配,如何通过这个ip偏移量获取到具体的ip地址?现在来解决这个问题。
一个网段里第一个ip的主机号全为0,被称为网络号,其ip偏移为0,拿192.168.0.0/16网段举例,第一个ip就是192.168.0.0,第二个ip地址其ip偏移量为1,ip地址是192.168.0.1,以此类推,可以得到下面的公式:
ip地址=ip网络号+ip地址偏移
所以关键就是要得到一个ip的ip网络号,用ipv4举例,在golang里面ip类型本质上就是一个长度为4字节数组
type IP []byte
所以现在要把这个4字节的数组转换为32位整形,可以像下面这样转换
func ipToUint32(ip net.IP) uint32 {
if ip == nil {
return 0
}
ip = ip.To4()
if ip == nil {
return 0
}
return binary.BigEndian.Uint32(ip)
}
func (bigEndian) Uint32(b []byte) uint32 {
_ = b[3] // bounds check hint to compiler; see golang.org/issue/14808
return uint32(b[3]) | uint32(b[2])<<8 | uint32(b[1])<<16 | uint32(b[0])<<24
}
由于ip地址是大端排序,网段号排在字节数组前面,所以binary.BigEndian进行转换。
这样获取ip的逻辑就是一个简单的加法了
firstIP := ipToUint32(ip.Mask(cidr.Mask))
ip = uint32ToIP(firstIP + uint32(pos))
关于ip的分配还有最后一个比较关键的点,那就是释放ip,前面已经提到我们已经可以办到释放第n个ip了,其中n就是ip的偏移量,那么如何通过ip地址去计算ip的偏移量呢?
其实很容易,拿当前ip减去网络号就是ip偏移量了
ip偏移量=当前ip-网络号ip
这里的具体代码我就不再展示了。
知道如何为容器分配ip地址了,还需要在网络命名空间内 创建新的网络设备,然后设置上这个ip。为了让整个逻辑变的简单,我们创建一个默认的网络,让容器创建的时候自动在这个默认的网络下,并为其分配ip。
整个过程分为两个阶段,一个是程序启动的时候,会去检查主机上是否存在这个默认网络需要的配置,如果有则不再创建相关网络设备,我将这个阶段称为网络初始化阶段,第二个阶段是容器创建时候,需要为容器创建相关网络配置的阶段。我们挨个来看看。
??? 当你写这部分代码时,最好用我这章源码上的根文件系统,因为原始的根文件系统其实是不包含ifconfig,ping命令的,到时候不好调试路由以及测试网络联通信,源码上的根文件系统我把这两个命令都下载好了。 ❗️❗️不过,你也可以用nsenter 命令进入容器的网络命名空间然后使用主机上的ping ifconfig 命令进行网络调试,类似这样nsenter -t 容器进程号 -n ping www.baidu.com
前面提到同一台主机上实现容器之间的网络互联,其本质是实现不同网络命名空间之间的互联,实现方式则是不同网络命名空间都用veth设备连接到一个网桥虚拟网络设备上,通过它们则可以实现网络互联。
所以在初始化阶段,首先就看看主机上的网桥设备是不是创建好了,并且设置一个网段作为默认网络将会使用的网段。
代码如下:
func Init() error {
// 对默认网络进行初试化
err := NetMgr.LoadConf()
if err != nil {
return fmt.Errorf("load netMgr conf err=%s", err)
}
if NetMgr.Storage[defaultNetName] == nil {
if err := BridgeDriver.CreateNetwork(defaultNetName, defaultSubnet, BridgeNetworkType); err != nil {
return fmt.Errorf("err=%s", err)
}
if err := IpAmfs.SetIpUsed(defaultSubnet); err != nil {
return err
}
}
return nil
}
为了实现主机上对默认网络的存储,我将默认网络的信息通过json序列化方式存储到了主机文件上。NetMgr.LoadConf则是对主机上存储的默认网络信息进行加载。加载之后判断是否存在默认网络,不存在则去创建一个默认网络,而创建一个默认网络的步骤本质上就是创建网桥设备,并设置网桥ip为defaultSubnet,然后将defaultSubnet的ip标记为使用。以便后续为容器分配ip地址时,不会占用网桥的ip地址。
接着着重来看下创建容器时如何进行相关的网络配置,我们需要在主机上创建一个veth设备,然后将这个veth设备一端连接到主机的网桥上,然后将另一端连接到容器的网络命名空间内部。
需要注意的是,在主机为容器配置好网络连接前,容器的子进程还必须进行等待,当主机为容器配置好网络设备后,子进程才真正的开始执行用户将要执行的程序。这里父子进程间的通信,我采用发送信号的方式,子进程等待父进程发送SIGUSR2信号,其代码如下
func main(){
.....
log.Info("Wait SIGUSR2 signal arrived ....")
// 等待父进程网络命名空间设置完毕
network.WaitParentSetNewNet()
........
err := syscall.Exec(cmd, os.Args[3:], os.Environ())
if err != nil {
log.Error("exec proc fail %s", err)
return
}
....
}
func WaitParentSetNewNet() {
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGUSR2)
<-sigs
log.Info("Received SIGUSR2 signal, prepare run container")
}
而父进程在用cmd.start命名启动子进程后则开始为容器子进程配置网络设备,代码如下
func ConfigDefaultNetworkInNewNet(pid int) error {
// 为容器分配ip
ip, err := IpAmfs.AllocIp(defaultSubnet)
if err != nil {
return fmt.Errorf("ipam alloc ip fail %s", err)
}
// 主机上创建 veth 设备,并连接到网桥上
vethLink, networkConf, err := BridgeDriver.CrateVeth(defaultNetName)
if err != nil {
return fmt.Errorf("create veth fail err=%s", err)
}
// 主机上设置子进程网络命名空间 配置
if err := BridgeDriver.setContainerIp(vethLink.PeerName, pid, ip, networkConf.BridgeIp); err != nil {
return fmt.Errorf("setContainerIp fail err=%s peername=%s pid=%d ip=%v conf=%+v", err, vethLink.PeerName, pid, ip, networkConf)
}
// 通知子进程设置完毕
return noticeSunProcessNetConfigFin(pid)
}
着重看下BridgeDriver.setContainerIp方法如何为对容器的网络命名空间进程配置。
func (b *bridgeDriver) setContainerIp(peerName string, pid int, containerIp net.IP, gateway *net.IPNet) error {
peerLink, err := netlink.LinkByName(peerName)
if err != nil {
return fmt.Errorf("fail config endpoint: %v", err)
}
loLink, err := netlink.LinkByName("lo")
if err != nil {
return fmt.Errorf("fail config endpoint: %v", err)
}
// 进入容器的网络命名空间
defer enterContainerNetns(&peerLink, pid)()
containerVethInterfaceIP := *gateway
containerVethInterfaceIP.IP = containerIp
// 为容器内部的veth设备设置ip
if err = setInterfaceIP(peerName, containerVethInterfaceIP.String()); err != nil {
return fmt.Errorf("%v,%s", containerIp, err)
}
// 将veth设备设置为up状态
if err := netlink.LinkSetUp(peerLink); err != nil {
return fmt.Errorf("netlink.LinkSetUp fail name=%s err=%s", peerName, err)
}
if err := netlink.LinkSetUp(loLink); err != nil {
return fmt.Errorf("netlink.LinkSetUp fail name=%s err=%s", peerName, err)
}
_, cidr, _ := net.ParseCIDR("0.0.0.0/0")
defaultRoute := &netlink.Route{
LinkIndex: peerLink.Attrs().Index,
Gw: gateway.IP,
Dst: cidr,
}
// 在容器的网络命名空间内配置路由信息
if err = netlink.RouteAdd(defaultRoute); err != nil {
return fmt.Errorf("router add fail %s", err)
}
return nil
}
整个过程其实比较清晰,通过veth设备名找到veth设备后,就进入了容器的网络命名空间,然后设备veth设备ip,设置路由表信息。关键在主机上要如何才能进入容器的网络命名空间呢?
答案是采用setns系统调用,setns系统调用可以让当前线程进程进入指定的命名空间内部,这段逻辑是在上述enterContainerNetns方法中,解释如下:
func enterContainerNetns(vethLink *netlink.Link, pid int) func() {
// 根据子进程pid查询网络命名空间的fd文件描述符
f, err := os.OpenFile(fmt.Sprintf("/proc/%d/ns/net", pid), os.O_RDONLY, 0)
if err != nil {
fmt.Println(fmt.Errorf("error get container net namespace, %v", err))
}
nsFD := f.Fd()
// 锁住当前线程,因为setns是让当前线程进指定命名空间,go的协程在运行时可以被挂载到不同的线程上去进行执行,为了规避这种情况,将当前协程与当前线程进行了绑定。
runtime.LockOSThread()
// 修改veth peer 另外一端移到容器的namespace中
if err = netlink.LinkSetNsFd(*vethLink, int(nsFD)); err != nil {
log.Error("error set link netns , %v", err)
}
// 获取当前的网络namespace
origns, err := netns.Get()
if err != nil {
log.Error("error get current netns, %v", err)
}
// 设置当前线程到新的网络namespace,并在函数执行完成之后再恢复到之前的namespace
if err = netns.Set(netns.NsHandle(nsFD)); err != nil {
log.Error("error set netns, %v", err)
}
return func() {
netns.Set(origns)
origns.Close()
runtime.UnlockOSThread()
f.Close()
}
}
#例12-8使用训练集和测试集,对iris数据进行分类 importnumpyasnp importmatplotlib.pyplotasplt fromsklearnimportdatasets fromsklearn.neural_networkimportMLPClassifier importpandasaspd fromsklearn.model_selectionimporttrain_test_split importjoblib iris=datasets.load_iris() #为可视化方便,取前2列作为特征属性 #print('iris的内容为:\n',iris) X=iris.data[:,:2]#不包括上限2 y=iris.target #划分训练集,测试集 X_train,X_test,y_train,y_test=train_test_split( X,y,train_size=0.8,random_state=42) hidden_n,hidden_m=10,6#隐层大小,2层,神经元数量分别为hidden_n,hidden_m cl
1.什么是消息队列消息队列,主要解决异步消息的管理(注册后,短信发送不是必须,可以使用队列)。实现系统之间的双向解耦,同时也能起到消息缓冲,消息分发的作用。当生产者产生大量数据,而消费者无法快速消费,(秒杀数据量过大使系统崩溃,队列可以废弃多余请求),或者是消费者异常了(服务挂掉后使请求丢失,队列可以保存请求)。说白话讲,主要作用就是异步,削峰与解耦。1.rabbitMQ简介1.运行流程rabbitmq是消息队列的一种,通过上图可以看到工作流程。生产者把请求给交换机,交换机把请求按照一定绑定关系发送给队列(平均发送),然后队列在把请求给消费者。其中交换机只负责转发并不负责保存,然后通过绑定关系与队列相绑定。交换器按照路由键绑定队列。当多消费者消费一个队列时,队列会均匀的发送到多个消费者之中。其中生产者为发送消息的服务/类。消费者是接收消息的服务/类。交换机将接收到的消息按照交换机类型发送给队列。未被消费的消息都被存放在队列中。2.交换机类型rabbitmq提供了四种交换机。fanout:发送给所有绑定该交换机的队列。Direct:默认的交换方法,按照提供的key去寻找队列。如果key为
精彩回顾 如何实现H5可视化编辑器的实时预览和真机扫码预览功能在线IDE开发入门之从零实现一个在线代码编辑器基于React+Koa实现一个h5页面可视化编辑器-DooringTS核心知识点总结及项目实战案例分析前言 笔者之前利于业余时间开发了一个gif动图生成平台,具体开发背景我也在上一篇文章手把手教你撸一个能生成抖音风格动图的gif制作平台中介绍过了,我们今天继续来实现该平台,gif动图平台的实现方式比较将完全用前端的手段来实现,所以大家在接下来的内容中会发现很多有意思的前端插件.接下来我们看看主要要实现的功能点:纯前端实现图片上传和预览基于react-beautiful-dnd实现元素自由拖动排序使用javascript实现生成uuid的函数使用file-saver实现前端下载文件使用gif.js实现基于图片生成gif动图控制gif动图播放速度的方法正文还是按照笔者一贯的风格,在实现功能之前我们先看看实现后的预览效果:由效果图可以看出我们只需要上传图片序列,就可以动态生成gif动图,并且可以右键保存gif文件.我们还可以控制动图的播放速度和排列顺序,以便更灵活的配置动图.如果大家想
CentOS下安装sudoyuminstallMySQL-python可以参考http://www.mikusa.com/python-mysql-docs/index.html 获取更多信息MySQL-python为Python提供MySQL驱动程序,主要包括两个部件,_mysql和MySQLdb连接数据库In [56]: import MySQLdb In [57]: db=MySQLdb.connect(host='127.0.0.1',user='xxx',passwd='xxx',db='xxx')复制2.创建游标 为了能够在多处使用同一个连接,可以创建一个游标对象In [60]: cur=db.cursor()复制3.执行MySQL查询操作创建数据库表In [62]: cur.execute("CREATE TABLE song (id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,title TEXT NOT NULL)")复制In [6
//Python2.x版本中,我们经常会用到异步的调用函数的功能,今天我们简单介绍一下异步执行Python函数的写法,要想实现异步调用Python函数,有几个概念需要了解。1、装饰器Python中的装饰器本质上的作用就是为已经存在的函数或者对象添加额外的逻辑功能。装饰器返回的对象也是一个函数对象,它经常被用在一些性能测试、日志追加、事务处理、权限校验等场景。有了装饰器,我们就可以将一些插件式的功能给拆分出来,简化代码逻辑。下面写一个简单的装饰器(翻译成英文就是wrapper)大家感受下:#encoding=utf-8 定义装饰器 defdebug(func): defwrapper(): print('[DEBUG]:enter{}()'.format(func.__name__)) print('hello,') returnfunc() returnwrapper #启用装饰器 @debug defsay_world(): print('world!') say_world() """ 执行
点击上方“芋道源码”,选择“设为星标”做积极的人,而不是积极废人!源码精品专栏原创|Java2019超神之路,很肝~ 中文详细注释的开源项目 RPC框架Dubbo源码解析网络应用框架Netty源码解析 消息中间件RocketMQ源码解析 数据库中间件Sharding-JDBC和MyCAT源码解析作业调度中间件Elastic-Job源码解析分布式事务中间件TCC-Transaction源码解析Eureka和Hystrix源码解析Java并发源码来源:http://t.cn/RDR5QVg1、获取今天的日期2、指定日期,进行相应操作3、生日检查或者账单日检查4、获取当前的时间5、日期前后比较6、处理不同时区的时间7、比较两个日期之前时间差8、日期时间格式解析、格式化9、java8时间类与Date类的相互转化1、获取今天的日期LocalDatetodayDate=LocalDate.now(); System.out.println("今天的日期:"+todayDate); //结果 今天的日期:2016-10-20 复制2、指定日期,进行相应操作//取2016年10月的
所谓变长参数指的是函数参数的数量不确定,可以按照需要传递任意数量的参数到指定函数,比如fmt.Printf函数的参数列表显然就是个变长参数。PHP中的变长参数简介PHP函数也支持变长参数,在PHP5.5及更早版本中,可以在定义函数时设置参数为空,然后在函数体中通过func_num_args()、func_get_arg()以及func_get_args()之类的函数获取参数数量及参数值:functionsum() { $sum=0; $numbers=func_get_args(); foreach($numbersas$number){ $sum+=$number; } printf("Thenumoftheargumentsare%d\n",func_num_args()); printf("Thesumofthenumbersare%d\n",$sum); } sum(1,2,3,4,5);复制从PHP5.6开始,变长参数的定义和其他语言的风格保持一致:functionsum(...$numbers) { $sum=0; foreach(
NewLife.XCode是一个有10多年历史的开源数据中间件,由新生命团队(2002~2019)开发完成并维护至今,以下简称XCode。整个系列教程会大量结合示例代码和运行日志来进行深入分析,蕴含多年开发经验于其中。开源地址:https://github.com/NewLifeX/X(求star,620+)生成实体类上一章《数据模型》讲到模型文件Model.xml和脚本Build.tt,(nuget安装NewLife.XCode后即可拥有)。把Build.tt和Model.xml(可改名)放在同一个目录,在Build.tt上右键“运行自定义工具”,“显示所有文件”,即可看到生成的实体类文件。**如果运行Build.tt出错,可能是因为找不到XCode.dll文件,可以先编译一次项目,让XCode.dll生成到项目输出目录即可我们来试试以下模型(拷贝到Model.xml里面):<?xmlversion="1.0"encoding="utf-8"?> <TablesVersion="9.9.6940.24706"
条件语句-----ifelse 似乎所有的条件语句都使用if.....else.....,它的作用可以简单地概括为非此即彼,满足条件A则执行A的语句,否则执行B语句,python的if......else......功能更加强大,在if和else之间添加数个elif,有更多的条件选择,其表达式如下:if判断条件1: 执行语句1elif判断条件2: 执行语句2elif判断条件3: 执行语句3else: 执行语句4编写一个test_if_else.py脚本,练习一下语法,代码如下:# coding: utf-8 __author__ = 'www.py3study.com' def isevennum(num): if num % 7 == 0: print(u'{}可以被7整除'.format(num)) else: print(u"{}不可被7整除".format(num)) if __name__ == '__main__': numstr
[提要]气候变化会对商品市场和行业造成影响,这使得天气预报领域变得非常重要且可盈利。传统气象学家通过研究实时气象形态来预报天气,对他们来说,历史温度数据在预报天气上的作用十分有限。气候变化会对商品市场和行业造成影响,这使得天气预报领域变得非常重要且可盈利。日前计算机学家向传统的气象学家发起了新一轮挑战,声称他们最终破解了预测气候的密码。由于极端天气会造成各类商品价格的大幅波动,银行和商家的利润和损失往往十分惊人;严寒天气和干旱也给电力供应商带来了更大压力。另一方面,越来越多的零售商和生产商开始利用天气预报管理库存。传统气象学家通过研究实时气象形态来预报天气,对他们来说,历史温度数据在预报天气上的作用十分有限。但一些曾在华尔街工作的应用数学家发掘了历史数据的可能性。经过多年研究,他们表示自己的气象演算法可以打败传统的气候预报法。数据气象公司(StatWeather)总裁,数据挖掘专家瑞尔·帕尔萨德(RiaPersad)对路透社说,气象学家主要研究正在发生的事实,用现在的数据预测未来,而她则通过研究近120年来的气候数据来归纳出气候模型。传统气象学家中的一些人也开始注意历史和实时数据相结合
1.热备方案 硬件:server两台,分别用于master-redis及slave-redis 软件:redis、keepalived 实现目标:由keepalived对外提供虚拟IP(VIP)进行redis访问主从redis正常工作,主负责处理业务,从进行数据备份当主出现故障时,从切换为主,接替主的业务进行工作当主恢复后,拷贝从的数据,恢复主身份,从恢复从身份数据采用aof方式进行持久化存储,秒级备份,当出现故障后,损失数据不超过1s Keepalived提供以下服务: 对用户提供VIP访问,屏蔽redis实际IP,当主机出现故障,仍可用VIP访问到从。对redis状态进行监控,将监控频率设置在1s。当主出现故障后能及时处理,切换从机提供业务。2.环境准备 利用虚拟机进行测试,安装ubuntu,安装完成后克隆ubuntu,利用两个虚拟机来构造服务器环境。 在两台虚拟机分别执行sudoapt-getredis-server和sudoapt-getkeepalived安装redis和keepalived软件。 配置两个虚拟机的redis主从关系: 1.保证两个虚拟机ip不一致且能互相p
项目介绍 Taro_Mall是一款多端开源在线商城应用程序,后台是基于litemall基础上进行开发,前端采用Taro框架编写,现已全部完成小程序和h5移动端,后续会对APP,淘宝,头条,百度小程序进行适配。Taro_Mall已经完成了litemall前端的所有功能 扫码体验 由于小程序没有认证,只发布了一个预览版,只能加15个人,如有需要,请点击小程序申请 小程序h5移动端 项目架构 项目用Taro做跨端开发框架,Taro基本采用React的写法,项目集成了reduxdva控制单向数据流,用immer来提供不可变数据,提升整体的性能,减少渲染。 初始化项目 taroinitTaro_Mall 复制 进入项目目录开始开发,可以选择小程序预览模式,或者h5预览模式,若使用微信小程序预览模式,则需要自行下载并打开微信开发者工具,选择预览项目根目录。 微信小程序编译和发布 yarndev:weapp//编译预览 yarnbuild:weapp//构建发布 复制 h5编译和发布 yarndev:h5//编译预览 yarnbuild:h5//构建发布 复制 其它端可以查看package.j
词嵌入模型 目录词嵌入模型词汇表征(WordRepresentation)使用one-hot表示的缺点特征化的表示使用词嵌入的步骤词嵌入的特性余弦相似度嵌入矩阵Word2VecSkip-Gram模型CBOW模型负采样Glove词向量Glove的具体实现Glove的训练方式代码 词汇表征(WordRepresentation) 使用one-hot表示的缺点 1.如果要表示的词语个数比较多的话,one—hot会很占空间。 2.如"Iwantaglassoforange()"与"Iwantaglassofapple()",填入的词语可以是juice,如果第一句话模型学习到的词语是juice,但是因为词语是one—hot向量表示,所以orange与apple之间的关系同orange与其他词并没有相对拉近,所以不利于模型的泛化。 特征化的表示 所以我们开始使用特征化的向量进行词语的表示,比如词语相对性别的关系:man可能为1,woman就是-1,king就是0.95,queen就是-0.97,而apple与orange没有性别可言。比如词语相对高贵:king与queen就相对分值较高。 假设最后
容器与数据耦合的问题 不便于修改:当我们要修改Nginx的html内容时,需要进入容器内部修改,很不方便。 数据不可复用:在容器内的修改对外是不可见的。所有修改对新创建的容器是不可复用的。 升级维护困难:数据在容器内,如果要升级容器必然删除旧容器,所有数据都跟着删除了 数据卷(volume)是一个虚拟目录,指向宿主机文件系统中的某个目录。 操作数据卷 基本语法如下: dockervolume[COMMAND]复制 dockervolume命令是数据卷操作,根据命令后跟随的command来确定下一步的操作: create创建一个volume inspect显示一个或多个volume的信息 ls列出所有的volume prune删除未使用的volume rm删除一个或多个指定的volume 创建一个数据卷,并查看数据卷在宿主机的目录位置 创建数据卷 dockervolumecreatehtml复制 查看所有数据卷 dockervolumels复制 查看数据卷详细信息卷 dockervolumeinspecthtml复制 挂载点 数据卷的作用: 将容器与数据分
<Windowx:Class="NetMonitor.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:NetMonitor" mc:Ignorable="d" BorderThickness="15" Title="MainWindow"Height="450"Width="800"AllowsTransparency="True"WindowStyle="None"> <Window.Effect> <DropShadowEf
TF-IDF可以用于特征提取,也可以用于特征权重计算,这里讲的是后者。 TF=文档d中特征词t的词频 IDF=log(文档总数/出现t的文档数) TF-IDF=TF*IDF (上式为weka中TFIDF计算公式,也是最经典的公式) 特征词的TFIDF权值代表了其在相应文档中的重要程度。 在朴素贝叶斯分类器中,TFIDF为numeric数值属性 此时P(H|X)=P(X|H)P(H)/P(X)中X为连续值属性,使用高斯分布 此时P(xk|Ci)=g(xk,μCi,σCi) TF-IDF应用在文本分类上也有一些问题 (1)如果特征集中出现在某类别,有类别区分能力,应该赋予较高权重 (2)文档长度对TF有一定影响,短文本TF值吃亏 (3)特征词仅高频出现在很少数文档,并不能表征类别,应赋予较低权重 当然也有改进公式 (1)TF=文档中t的词频/文档总长度,以此消除长短文本的不平衡 (2)结合类内分布、类间分布的评估,类内分布越多权值越高,类间分布越平均权重越低 -----------------------------------