探讨下如何更好的使用缓存 —— Redis缓存的特殊用法以及与本地缓存一起构建多级缓存的实现

大家好,又见面了。


本文是笔者作为掘金技术社区签约作者的身份输出的缓存专栏系列内容,将会通过系列专题,讲清楚缓存的方方面面。如果感兴趣,欢迎关注以获取后续更新。


通过前面的文章,我们一起剖析了Guava CacheCaffeineEhcache本地缓存框架的原理与使用场景,也一同领略了以Redis为代表的集中式缓存在分布式高并发场景下无可替代的价值。

现在的很多大型高并发系统都是采用的分布式部署方式,而作为高并发系统的基石,缓存是不可或缺的重要环节。项目中使用缓存的目的是为了提升整体的运算处理效率、降低对外的IO请求,而集中式缓存是独立于进程之外部署的远端服务,需要基于网络IO的方式交互。如果一个业务逻辑中涉及到非常频繁的缓存操作,势必会导致引入大量的网络IO交互,造成过大的性能损耗、加剧缓存服务器的压力。另外,对于现在互联网系统的海量用户数据,如何压缩缓存数据占用容量,也是需要面临的一个问题。

本篇文章,我们就一起聊一聊如何来更好的使用缓存,探寻下如何降低缓存交互过程的性能损耗、如何压缩缓存的存储空间占用、如何保证多个操作命令原子性等问题的解决策略,让缓存在项目中可以发挥出更佳的效果。

通过BitMap降低Reids存储容量压力

在一些互联网类的项目中,经常会有一些签到相关功能。如果使用Redis来缓存用户的签到信息,我们一般而言会怎么存储呢?常见的会有下面2种思路:

  1. 使用Set类型,每天生层1个Set,然后将签到用户添加到对应的Set中;
  2. 还是使用Set类型,每个用户一个Set,然后将签到的日期添加到Set中。

对于海量用户的系统而言,按照上述的策略,那么每天仅签到信息这一项,就可能会有上千万的记录,一年累积下来的数据量更大 —— 这对Redis的存储而言是笔不小的开销。对于签到这种简单场景,只有签到和没签到两种情况,也即0/1的场景,我们也可以通过BitMap来进行存储以大大降低内存占用。

BitMap(位图)可以理解为一个bit数组,对应bit位可以存放0或者1,最终这个bit数组被转换为一个字符串的形式存储在Redis中。比如签到这个场景,我们可以每天设定一个key,然后存储的时候,我们可以将数字格式的userId表示在BitMap中具体的位置信息,而BitMap中此位置对应的bit值为1则表示该用户已签到。

Redis其实也提供了对BitMap存储的支持。前面我们提过Redis支持String、Set、List、ZSet、Hash等数据结构,而BitMap能力的支持,其实是对String数据结构的一种扩展,使用String数据类型来支持BitMap的能力实现。比如下面的代码逻辑:

public void userSignIn(long userId) {
    String today = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
    String redisKey = "UserSginIn_" + today;
    Boolean hasSigned = stringRedisTemplate.opsForValue().getBit(redisKey, userId);
    if (Boolean.TRUE.equals(hasSigned)) {
        System.out.println("今日已签过到!");
    } else {
        stringRedisTemplate.opsForValue().setBit("TodayUserSign", userId, true);
        System.out.println("签到成功!");
    }
}

对于Redis而言,每天就只有一条key-value数据。下面对比下使用BitMap与使用普通key-value模式的数据占用情况对比。模拟构造10亿用户数据量进行压测统计,结果如下:

  • BitMap格式: 150M
  • key-value格式: 41G

可以看出,在存储容量占用方面,BitMap完胜。

关于pipeline管道批处理与multi事务原子性

使用Pipeline降低与Reids的IO交互频率

在很多的业务场景中,我们可能会涉及到同时去执行好多条redis命令的操作,比如系统启动的时候需要将DB中存量的数据全部加载到Redis中重建缓存的时候。如果业务流程需要频繁的与Redis交互并提交命令,可能会导致在网络IO交互层面消耗太大,导致整体的性能降低。

这种情况下,可以使用pipeline将各个具体的请求分批次提交到Redis服务器进行处理。

private void redisPipelineInsert() {
    stringRedisTemplate.executePipelined(new SessionCallback() {
        @Override
        public Object execute(RedisOperations operations) throws DataAccessException {
            try {
                // 具体的redis操作,多条操作都在此处理,最后会一起提交到Redis远端去执行
            } catch (Exception e) {
                log.error("failed to execute pipelined...", e);
            }
            return null;
        }
    });
}

使用pipeline的方式,可以减少客户端与redis服务端之间的网络交互频次,但是pipeline也只是负责将原本需要多次网络交互的请求封装一起提交到redis上,在redis层面其执行命令的时候依旧是逐个去执行,并不会保证这一批次的所有请求一定是连贯被执行,其中可能会被插入其余的执行请求。

也就是说,pipeline的操作是不具备原子性的。

使用multi实现请求的事务

前面介绍pipeline的时候强调了其仅仅只是将多个命令打包一起提交给了服务器,然后服务器依旧是等同于逐个提交上来的策略进行处理,无法保证原子性。对于一些需要保证多个操作命令原子性的场景下,可以使用multi来实现。

当客户端请求执行了multi命令之后,也即开启了事务,服务端会将这个客户端记录为一个特殊的状态,之后这个客户端发送到服务器上的命令,都会被临时缓存起来而不会执行。只有当收到此客户端发送exec命令的时候,redis才会将缓存的所有命令一起逐条的执行并且保证这一批命令被按照发送的顺序执行、执行期间不会被其他命令插入打断。

代码示例如下:

private void redisMulti() {
    stringRedisTemplate.multi();
    stringRedisTemplate.opsForValue().set("key1", "value1");
    stringRedisTemplate.opsForValue().set("key2", "value2");
    stringRedisTemplate.exec();
}

需要注意的一点是,redis的事务与关系型数据库中的事务是两个不同概念,Redis的事务不支持回滚,只能算是Redis中的一种特殊标记,可以将这个事务范围内的请求以指定的顺序执行,中间不会被插入其余的请求,可以保证多个命令执行的原子性。

pipeline与multi区别

从上面分别对pipelinemulti的介绍,可以看出两者在定位与功能分工上的差异点:

  • pipeline是客户端行为,只是负责将客户端的多个请求一次性打包传递到服务器端,服务端依旧是按照和单条请求一样的处理,批量传递到服务端的请求之间可能会插入别的客户端的请求操作,所以它是无法保证原子性的,侧重点在于其可以提升客户端的效率(降低频繁的网络交互损耗)

  • multi是服务端行为,通过开启事务缓存,保证客户端在事务期间提交的请求可以被一起集中执行。它的侧重点是保证多条请求的原子性,执行期间不会被插入其余客户端的请求,但是由于开启事务以及命令缓存等额外的操作,其对性能略微有一些影响。

多级缓存机制

本地+远端的二级缓存机制

在涉及与集中式缓存之间频繁交互的时候,通过前面介绍的pipeline方式可以适当的降低与服务端之间网络交互的频次,但是很多情况下,依旧会产生大量的网络交互,对于一些追求极致性能的系统而言,可能依旧无法满足诉求。

回想下此前文章中花费大量篇幅介绍的本地缓存,本地缓存在分布式场景下容易造成数据不一致的问题,但是其最大特点就是快,因为数据都存储在进程内。所以可以将本地缓存作为集中式缓存的一个补充策略,对于一些需要高频读取且不会经常变更的数据,缓存到本地进行使用。

常见的本地+远端二级缓存有两种存在形式。

  • 独立划分,各司其职

这种情况,将缓存数据分为了2种类型,一种是不常变更的数据,比如系统配置信息等,这种数据直接系统启动的时候从DB中加载并缓存到进程内存中,然后业务运行过程中需要使用时候直接从内存读取。而对于其他可能会经常变更的业务层面的数据,则缓存到Redis中。

  • 混合存储,多级缓存

这种情况可以搭配Caffeine或者Ehcache等本地缓存框架一起实现。首先去本地缓存中执行查询,如果查询到则返回,查询不到则去Redis中尝试获取。如果Redis中也获取不到,则可以考虑去DB中进行回源兜底操作,然后将回源的结果存储到Redis以及本地缓存中。这种情况下需要注意下如果数据发生变更的时候,需要删除本地缓存,以确保下一次请求的时候,可以再次去Redis拉取最新的数据。

本地+远端的二级缓存机制有着多方面的优点:

  • 主要操作都在本地进行,可以充分的享受到本地缓存的速度优势

  • 大部分操作都在本地进行,充分降低了客户端与远端集中式缓存服务器之间的IO交互,也降低了带宽占用

  • 通过本地缓存层,抵挡了大部分的业务请求,对集中式缓存服务器端进行减压,大大降低服务端的压力

  • 提升了业务的可靠性,本地缓存实际上也是一种额外的副本备份,极端情况下,及时集中式缓存的服务端宕机,因为本地还有缓存数据,所以业务节点依旧可以对外提供正常服务。

二级缓存的应用身影

其实,在C-S架构的系统里面,多级缓存的概念使用的也非常的频繁。经常Clinet端会缓存运行时需要的业务数据,然后采用定期更新或者事件触发的方式从服务端更新本地的数据。而Server端负责存储所有的数据,并保证数据更新的时候可以提供给客户端进行更新获取。

一个典型的例子,就是分布式系统中的配置中心或者是服务注册管理中心。比如SpringCloud家族的Eureka,或者是Alibaba开源的Nacos。它们都有采用客户端本地缓存+服务端数据统一存储的方式,来保证整体的处理效率,降低客户端对于Server端的实时交互依赖。

看一下Nacos的交互示意:

从图中可以表直观的看到,Client将业务数据缓存到各自本地,这样业务逻辑进行处理的时候就可以直接从本地缓存中查询到相关的业务节点映射信息,而Server端只需要负责在数据有变更的事后推送到Client端更新到本地缓存中即可,避免了Server端去承载业务请求的流量压力。整体的可靠性也得到了保证,避免了Server端异常对业务正常处理造成影响。

小结回顾

好啦,到这里呢,《深入理解缓存原理与实战设计》系列专栏的内容就暂告一段落咯。本专栏围绕缓存这个宏大命题进行展开阐述,从缓存各种核心要素、到本地缓存的规范与标准介绍,从手写本地缓存框架、到各种优秀本地缓存框架的上手与剖析,从本地缓存到集中式缓存再到最后的多级缓存的构建,一步步全方位、系统性地做了介绍。希望通过本专栏的介绍,可以让大家对缓存有个更加深刻的理解,可以更好的在项目中去使用缓存,让缓存真正的成为我们项目中性能提升的神兵利器

看到这里,不知道各位小伙伴们对缓存的理解与使用,是否有了新的认识了呢?你觉得缓存还有哪些好的使用场景呢?欢迎评论区一起交流下,期待和各位小伙伴们一起切磋、共同成长。

我是悟道,聊技术、又不仅仅聊技术~

如果觉得有用,请点赞 + 关注让我感受到您的支持。也可以关注下我的公众号【架构悟道】,获取更及时的更新。

期待与你一起探讨,一起成长为更好的自己。

本文来自博客园,作者:架构悟道,欢迎关注公众号[架构悟道]持续获取更多干货,转载请注明原文链接:http://www.cnblogs.com/softwarearch/p/16937368.html

本文转载于网络 如有侵权请联系删除

相关文章

  • 主成分分析

    importnumpyasnp fromsklearn.datasetsimportload_digits importmatplotlib.pyplotasplt fromsklearn.decompositionimportPCA fromsklearn.preprocessingimportscale fromsklearn.discriminant_analysisimportLinearDiscriminantAnalysisasLDA importmatplotlib.cmascm digits=load_digits() data=digits.data n_samples,n_features=data.shape n_digits=len(np.unique(digits.target)) labels=digits.target pca=PCA(n_components=10) data_r=pca.fit(data).transform(data) print('explainedvarianceratio(firstcomponents):%s

  • 彩色图像色彩空间原理

    自然界的各种色彩、人类所感知的色彩以及各种图像设备和计算机软件所使用的颜色可通过色彩空间(ColorSpace)来描述。色彩是人脑对不同视觉刺激的反应。人眼视网膜上的色敏细胞会分别对红、绿、蓝3个波段的色彩进行采样。采样后的信号传送至大脑后组合在一起就会产生对色彩的感知。由于颜色是大脑对特定视觉刺激的反应,因此颜色最好也由人类大脑对不同颜色的感觉来描述。据此人们创建了由多个颜色分量来表示颜色的模型,这些模型被称为色彩空间。色彩空间是指通过多个(通常为3个或4个)颜色分量构成坐标系来表示各种颜色的模型系统。色彩空间中的每个点均代表一种颜色,也就是说各点的颜色可看作是多个分量的合成。例如,在RGB色彩空间中,颜色可认为是红(Red)、绿(Green)、蓝(Blue)3种颜色分量的加性合成;在HSL色彩空间中,颜色可认为是色调(Hue)、饱和度(Saturation)和亮度 (Luminance/Lightness)的合成。基于这种思想,机器视觉系统开发过程中待处理的彩色图像就可根据需要被映射至某个色彩空间上进行描述。因此彩色图像的处理计算工作就可以被分解至各颜色分量所对应的一组图像上进行,

  • python二叉树

    参考链接:Python|a+=b并不总是a=a+b1.树的特征和定义  树是一种重要的非线性数据结构,直观地看,它是数据元素(在树中称为结点)按分支关系组织起来的结构,很象自然界中的树那样。树结构在客观世界中广泛存在,如人类社会的族谱和各种社会组织机构都可用树形象表示。树在计算机领域中也得到广泛应用,如在编译源程序时,可用树表示源程序的语法结构。又如在数据库系统中,树型结构也是信息的重要组织形式之一。一切具有层次关系的问题都可用树来描述。 树(Tree)是元素的集合。我们先以比较直观的方式介绍树。下面的数据结构是一个树:  树有多个节点(node),用以储存元素。某些节点之间存在一定的关系,用连线表示,连线称为边(edge)。边的上端节点称为父节点,下端称为子节点。树像是一个不断分叉的树根。  每个节点可以有多个子节点(children),而该节点是相应子节点的父节点(parent)。比如说,3,5是6的子节点,6是3,5的父节点;1,8,7是3的子节点,3是1,8,7的父节点。树有一个没有父节点的节点,称为根节点(root),如图中的6。没有子节点的节点称为叶节点(leaf),比如图

  • 面试热点|理解TCP/IP传输层拥塞控制算法

    0x00.前言这是TCP/IP协议栈系列的第二篇文章,之前的一篇理解TCP/IP协议栈之HTTP2.0感兴趣可以看下,今天一起来学习下一个热点问题。通过本文你将了解到以下内容:拥塞控制概念以及其背景流量控制和拥塞控制的区别与联系拥塞控制主要过程详解 伙伴们认真学习一下,让offer来得更猛烈些吧!0x01.TCP/IP协议栈简要回顾来看下维基百科对TCP/IP的一些介绍,笔者做了少量的修改来确保语句通顺:互联网协议套件是一个网络通信模型以及整个网络传输协议家族,由于该协议簇包含两个核心协议:TCP(传输控制协议)和IP(网际协议),因此常被通称为TCP/IP协议族。TCP/IP协议对于数据应该如何封装、定址、传输、路由以及在目的地如何接收等基本过程都加以标准化。它将通信过程抽象化为四个层次,并采取协议堆栈的方式分别实现出不同通信协议,实际使用的四层结构是七层OSI模型的简化。我们可以看到TCP/IP协议栈是一个简化的分层模型,是互联网世界连接一切的基石,一起来看一张七层模型vs四层模型的简图:0x02.流量控制和拥塞控制TCP是一种面向连接的、可靠的、全双工传输协议,前辈们写了很多复

  • 牛逼!一行代码居然能解决这么多曾经困扰我半天的算法题

    来源公众号:帅地玩编程 作者:帅地春节假期这么长,干啥最好?当然是折腾一些算法题了,下面给大家讲几道一行代码就能解决的算法题,当然,我相信这些算法题你都做过,不过就算做过,也是可以看一看滴,毕竟,你当初大概率不是一行代码解决的。学会了一行代码解决,以后遇到面试官问起的话,就可以装逼了。一、2的幂次方问题描述:判断一个整数n是否为2的幂次方对于这道题,常规操作是不断着把这个数除以2,然后判断是否有余数,直到n被整除成1。我们可以把n拆成二进制看待处理的,如果n是2的幂次方的话,那么n的二进制数的最高位是1,后面的都是0,例如对于16这个数,它的二进制表示为10000。如果我们把它减1,则会导致最高位变成0,其余全部变成1,例如10000-1=01111。然后我们把n和(n-1)进行与操作,结果就会是0,例如(假设n是16)n&(n-1)=10000&(10000-1)=10000&01111=0也就是说,n如果是2的幂次方,则n&(n-1)的结果是0,否则就不是,所以代码如下intisPow(n){ return(n&(n-1))==0; } 复制

  • 《统计学习方法》笔记-统计学习方法概论-1

    统计学习的定义、研究对象与方法监督学习,这是本书的主要内容统计学习方法的三要素模型策略算法模型选择正则化交叉验证学习的泛化能力生成模型与判别模型监督学习方法的应用分类问题标注问题回归问题。1.1 统计学习1.统计学习的特点统计学习(statisticallearning)是关于计算机基于数据构建概率统计模型并运用模型对数据进行预测与分析的一门学科。统计学习也称为统计机器学习(statisticalmachinelearning)。统计学习的主要特点是统计学习以计算机及网络为平台,是建立在计算机及网络之上的统计学习以数据为研究对象,是数据驱动的学科统计学习的目的是对数据进行预测与分析统计学习以方法为中心,统计学习方法构建模型并应用模型进行预测与分析统计学习是概率论、统计学、信息论、计算理论、最优化理论及计算机科学等多个领域的交叉学科,并且在发展中逐步形成独自的理论体系与方法论。赫尔伯特·西蒙(HerbertA.Simon)曾对“学习”给出以下定义“如果一个系统能够通过执行某个过程改进它的性能,这就是学习。” 按照这一观点,统计学习就是计算机系统通过运用数据及统计方法提高系统性能的机器学习

  • TEMPORARY_DISABLE_PATH_RESTRICTIONS was a temporary migration method, and is now obsolete.

       解决办法: exportTEMPORARY_DISABLE_PATH_RESTRICTIONS=false 复制   

  • vue动态添加class 三个以上的条件做判断(转)

    原文:https://blog.csdn.net/Akatsuki233/article/details/100653049 :class="{'redRoom':Number(items.status)===1,'greenRoom1':Number(items.status)===2,greenRoom2:Number(items.status)>2}" 复制 如果status为1,样式为redRoom 如果status为2,样式为greenRoom1 如果status为其他,样式为greenRoom2

  • JavaScript语法变量一元运算符以及算数和比较运算符

    JavaScript-语法-变量-一元运算符 运算符:   一元运算符:只有一个运算数的运算符     ++  --  +(正号)     ++  --  :自增(自减)       ++(--):在前,先自增(自减),再运算       ++(--):在后,先运算,再自增(自减)     +:正负号     注意:在JS中,如果运算数不是运算符所要求的类型,那么js引擎会自动的将运算数进行类型转换       其他类型转换number        string转number:按照字面值转换,如果字面值不是数字,则转为NaN(不是数字的数字)        boolean转number:true转为1,false转为0 <script> /* 一元运算符:只有一个运算数的运算符   ++,--(+)正号   ++,--自增(自减)     ++(--)在前,先自增(自减),在进行计算     ++(--)在后,先进行计算,在自增(自减) +(-):正负号 注意:在JS中,如果运算数不是运算符

  • 硬件对同步的支持-TAS和CAS指令

    目录TestandSetCompareandSwap使用CAS实现线程安全的数据结构。 现在主流的多处理器架构都在硬件水平上提供了对并发同步的支持。 今天我们讨论两个很重要的硬件同步指令:Test-and-Set和Compare-and-Swap TestandSet 一个Test-and-Set(TAS)指令包括两个子步骤,把给定的内存地址设置为1,然后返回之前的旧值。 这两个子步骤在硬件上实现为一个原子操作,执行期间不会被其他处理器打断。 (一个CPU可以使用诸如Dual-portRAM电子原件提供的TAS指令,此外,CPU自身也可以提供CAS指令) 值得注意的是,TAS指令是在单个内存word(或字节)上实现,这限制了变量非0即1,并且TAS总是把变量设置为1。 可见,TAS生而为自旋锁,下面是使用TAS实现自旋锁的伪代码[2]: lock=0//sharedstate while(test_and_set(lock)==0){//trylock //donothing } //临界区代码 lock=0//release 复制 当第一个线程执行这段代码时,TAS指令会立即把loc

  • 第一次课 圆面积

    第一次课圆面积 github上代码地址点这里 感觉没有什么特别多需要注意的地方吧。。 就是pi用arccos(-1.0)表示,精度会稍高一些。。

  • 个人总结

      时间过的飞快,转眼间软件工程这门课也迎来了尾声,刚接触这门课时,我的内心只有一种想法:我好菜啊……啥都不会啊……这门课在干啥啊……为什么当初要选这门课啊……。但是随着课程的深入,每周(后来改成两周)阅读一本软件工程的书籍以及个人项目、结对项目、团队项目的不断展开,我逐渐对这门课有了更深刻的认识。在此期间,遇到了许多困难,同样的也收获了很多新的知识、技能。   个人作业 老师给出的题目是词频统计。对于这道题目,实际上对算法要求并不高,但是在此期间解决了很多以前没有注意的问题,比如:代码规范、代码热行。以前写代码时,想到什么就写什么,没有考虑到代码规范,写出来的代码自然就很难看(hhh~)。 通过个人作业我学会了: 代码规范 制作PSP表格,来规划时间,提升效率 GitHub的使用 如何将代码移植到Linux系统上   结对作业 我做的是core组,比起个人作业多出了两个人交流以及分工的部分,两人分别承担驾驶员和领航员的角色的模式,“驾驶员”负责具体的编码工作,“领航员”则负责检查,及时纠正代码中的问题。结对编程的形式使得代码处于不

  • git clone 指定的单个目录或文件夹

    gitclone指定的单个目录或文件夹 针对自己的项目 方法一 基于sparseclone变通方法 创建一个空仓库 拉取远程仓库信息 开启sparseclone 设置过滤 更新仓库 创建空仓库 mkdirdevops cddevops gitinit#初始化 复制 拉取远程仓库信息 gitremoteadd-foriginhttp://your/git/repo.git#拉取远程仓库信息 复制 开启sparseclone gitconfigcore.sparsecheckouttrue#开启sparseclone 复制 设置过滤 echo"devops">>.git/info/sparse-checkout#设置过滤条件 复制 更新仓库 gitpulloriginmaster#拉取仓库 复制 方法二(可能没用) 使用svn 打开对应目录 将其url中的/tree/master/更换为/trunk/ 使用svn下载 文件的url:https://github.com/Mooophy/Cpp-Primer/tree/master/ch03##将/tree/master/

  • CF C. Restoring the Duration of Tasks #797 div3

    题意就是给你一个项目开始的时间点和结束的时间点,看看每一个duration是多少 hh,这题刚开始思路不是那么明朗,手搓一个大模拟?又臭又长,然后再重新捋一下思路,其实就是把小于结束点的那个时间点改成那个结束点 #include<iostream> usingnamespacestd; constintN=2e6+10; inta[N],b[N]; intmain(){ intT; cin>>T; while(T--) { intn; scanf("%d",&n); for(inti=1;i<=n;i++)scanf("%d",&a[i]); for(inti=1;i<=n;i++)scanf("%d",&b[i]); for(inti=1;i<=n;i++) { if(a[i+1]>=b[i])printf("%d",b[i]-a[i]); else { a[i+1]=b[i]; printf("%d",b[i]-a[i]); } } printf("\n"); } return0; }复制 就是区间覆盖不能

  • 基础拓扑学讲义 1.7 粘合映射不是开映射

    粘合映射不是开映射 定义 粘合映射\(p\):\(p\)是等价关系诱导出的映射,故而必为满射。 \((X,\tau)\)是拓扑空间,\(\sim\)是集合\(X\)上的一个等价关系,规定商集\(X/\sim\)上的子集族 \[\tilde{\tau}:=\{V\subsetX/\sim|p^{-1}(V)\in\tau\} \]则\(\tilde{\tau}\)是\(X/\sim\)上的一个拓扑,称为\(\tau\)在\(\sim\)下的商拓扑。 换个说法就是\(X/\sim\)的所有原像为开集的子集都是开集,这是否说明\(p\)是开映射? 答案是并不,为了说明它不是开映射,我们需要找到\(p\)把开集映射到非开集的反例。 首先简单分析一下,假设\(p(U)=V\),\(U\)为开集,\(V\)不为开集。根据\(\tilde{\tau}\)的定义,\(p^{-1}(V)\)不为开集。那么思路来了,只需要取\(X\)上闭集\(A\)使得\(p(A)\subsetV\)即可。 U A V=p(U) p(A) 同胚映射\(f\) 同胚=双射+连续+开/闭映射

  • HTTP报文基本知识

     客户端与服务器用HTTP通信时通过报文来传递信息。HTTP报文可分为两类:请求报文、响应报文。 1、请求报文:客户端向服务器请求一个动作。一般格式是: <method><request-URL><version> <headers> <entity-body>复制   其中,method代表希望服务器执行的动作,如GET、POST、HEAD等;request-URL命名了所请求资源的完整URL;version代表报文使用的HTTP版本,格式为:HTTP/<major>.<minor>;headers是HTTP首部;entity-body代表实体的主体部分。   常用的HTTP方法: 方法 描述 是否包含主体 GET 从服务器获取一份文档 否    HEAD 只从服务器获取文档首部 否 POST 向服务器发送数据 是 PUT 将请求的主体部分存储在服务器上 是 TRACE 对可能经过代理服务器传送到服务器上去的报文进行追踪 否 OPTIONS 决定可以在服务

  • 《数学分析》笔记:实数集和函数 1

    §1实数 一定义 定义1给定两个非负实数 \[x=a_0.a_1a_2\cdotsa_n\cdots,\y=b_0.b_1b_2\cdotsb_n\cdots, \]其中\(a_0,b_0\)为非负整数,\(a_k,b_k(k=1,2,\cdots)\)为整数,\(0\leqslanta_k\leqslant9,0\leqslantb_k\leqslant9.\)若有 \[a_k=b_k,k=0,1,2,\cdots, \]则称\(x\)与\(y\)相等,记为\(x=y;\)若\(a_0>b_0\)或存在非负整数\(l\)使得 \[a_k=b_k(k=0,1,2,\cdots,l)\text{而}a_{l+1}>b_{l+1}, \]则称\(x\)大于\(y\)或\(y\)小于\(x,\)分别记为\(x>y\)或\(y<x.\) 对于负实数\(x,y\),若按上述规定分别有\(-x=-y\)与\(-x>-y\),则分别称\(x=y\)与\(x<y\)\((\)或\(y>x).\)另外,规定任何非负实数大于任何负实数. 定义2设\(x=a_0.a

  • phpStudy for Linux (lnmp+lamp一键安装包)

    下载地址: 下载版:http://lamp.phpstudy.net/phpstudy.bin 完整版:http://lamp.phpstudy.net/phpstudy-all.bin PHP环境支持: 解释:Apache/Nginx/Tengine/Lighttpd PHP版本:支持php5.2/5.3/5.4/5.5切换  操作系统:已经在centos-6.5,debian-7.4.,ubuntu-13.10测试成功 安装方法: 下载好.bin文件 cd到下载目录 chmod+x./phpstudy.bin 给权限 然后在终端中运行./phpstudy.bin 等待编译完成 使用方法 在终端中使用sudo或者使用管理员账号运行phpstudystart开启 命令列表: phpstudystart|stop|restart        开启|停止|重启 phpstudyadd|del|list      

  • Unity导航 (寻路系统Nav Mesh Agent)

    第一种简单寻路地面接触到的。到达目标点不用跳跃能够一直走路到达。场景视图中简单搭设几个物体。胶囊体为寻路者,黄球为目标点红地板,绿色障碍物。现将地板以及障碍物选中在检视面板设置静态为NavigationStatic如图2然后菜单栏选择窗口Window–Navigation然后选择All—Bake烘焙如图有三个页面参数可以调节烘焙效果,参数具体参照圣典解释。注意胶囊体和目标物都要烘焙在蓝色格子里面。否则不能实现,可以调节第二个页面里的参数调节烘焙大小。然后给胶囊体添加导航组件菜单栏Component—Navigation-NavMeshAgent组件参数详情参见圣典.然后给胶囊体添加代码,代码内容如图aa即为目标物。在检视面板将球给aa。运行即可实现简单寻路。如图第二种寻路不连续,可以跳跃的寻路先搭设简单场景如图如果跳跃必须加三个跳板如图长条白板将地面和高台(绿色)一起设置为NavigationStatic给中间跳板加一个组件Component—Navigation—OffMeshLink其中有几个属性start和end这就是起跳位置和落地位置,将两个跳板用鼠标拖拽上即可。胶囊体添加寻路

  • CNN实操记录(goal:一日一更新)

    导师说:“最快的学习方式是看别人的博客,然后实操。你需要实操。” 开始进行CNN实操记录!尽量一日一更新,依照忙的程度进行略微调整,看能坚持多久!争取在下个月汇报前做出点东西。 --------------------------------------------------------------------------------------------------- 4.3. http://www.cnblogs.com/dupuleng/articles/4242906.htmlMNIST+再次阅读官方tutorial

  • HDOJ-1232 畅通工程【并查集裸题】

    题目传送门: http://acm.hdu.edu.cn/showproblem.php?pid=1232   并查集的裸题   ACcode: #include<iostream> #defineMAXN1050 usingnamespacestd; intpre[MAXN]; intFind(intpos){ intr=pos; while(r!=pre[r]) r=pre[r]; inti=pos; while(i!=r){ intt=pre[i]; pre[i]=r; i=t; } returnr; } voidJoin(intposX,intposY){ introotX=Find(posX),rootY=Find(posY); if(rootX!=rootY) pre[rootX]=rootY; } intmain() { intN,M; while(cin>>N>>M&&N){ intct=0; for(inti=1;i<=N;++i) pre[i]=i; intcityA,cit

相关推荐

推荐阅读