1. NoSql 简介

2. Redis 简介

3. 缓存异常

1. NoSQL 介绍

NoSQL(Not Only SQL)指的是非关系型的数据库。随着访问量的上升,传统的关系型数据库性能容易出现问题,于是 NoSQL 被设计出来,它的出现主要是为了解决传统数据库系统的规模问题。

NoSQL 常用于超大规模数据的存储(例如 google 或 facebook 每天为他们的用户收集万亿比特的数据)。这些类型的数据存储不需要固定的模式,无需多余操作就可以横向扩展。

CAP定理(CAP theorem)

在计算机科学中, CAP定理(CAP theorem), 又被称作布鲁尔定理(Brewer's theorem), 它指出对于一个分布式计算系统来说,不可能同时满足以下三点:

  • 一致性(Consistency):所有节点在同一时间具有相同的数据。
  • 可用性(Availability):保证每个请求不管成功或者失败都有响应。
  • 分隔容忍(Partition tolerance):系统中任意信息的丢失或失败不会影响系统的继续运作。

CAP 理论的核心是:一个分布式系统不可能同时很好的满足一致性、可用性和分区容错性这三个需求,最多只能同时较好的满足两个。

因此,根据 CAP 原理将 NoSQL 数据库分成了满足 CA 原则、满足 CP 原则和满足 AP 原则三大类:

  • CA -- 单点集群,满足一致性,可用性的系统,通常在可扩展性上不太强大。
  • CP -- 满足一致性、分区容忍性的系统,通常性能不是特别高。
  • AP -- 满足可用性、分区容忍性的系统,通常可能对一致性要求低一些。

BASE

BASE(Basically Available, Soft-state, Eventually Consistent)是 NoSQL 数据库通常对可用性及一致性的弱要求原则:

  • Basically Available -- 基本可用。
  • Soft-state -- 软状态/柔性事务。 "Soft state" 可以理解为"无连接"的, 而 "Hard state" 是"面向连接"的。
  • Eventually Consistency -- 最终一致性, 也是 ACID 的最终目的。

NoSQL 的优点/缺点

优点

  • 灵活的数据模型。NoSQL 数据结构比关系型数据库的更丰富,传统关系型数据库都是结构化的表,而 NoSQL 可以是列式存储、key-value、文档存储、图存储等。
  • 更易扩展。像 NoSQL 数据库分分钟就可以添加一台新的服务器。
  • 查询效率高。传统关系型数据库受限于磁盘 I/O,所以在高并发的情况下,压力倍增,而像 Redis 这种内存数据库每秒支持 10w 次读写。
  • NoSQL 授权费用也比较低,相比较 Oracle 这种企业级授权费用是低了不少。

缺点

  • 没有标准化:不支持 sql 这样的工业标准查询,所以学习成本就比较高。
  • 大多数 NoSQL 都不支持事务(Redis 支持,MongoDB 不支持)。
  • NoSQL 只能保证数据相对一致性,尤其是在数据同步的时候,主从服务器的状态是不一致的。
  • 大多都是初创产品,不够成熟,和传统数据库几十年的完善不可同日而语。

NoSQL 数据库分类

类型 部分代表 特点
列存储

Hbase

Cassandra

Hypertable

顾名思义,是按列存储数据的。最大的特点是方便存储结构化和半结构化数据,方便做数据压缩,对针对某一列或者某几列的查询有非常大的 I/O 优势。

文档存储

MongoDB

CouchDB

文档存储一般用类似 json 的格式存储,存储的内容是文档型的。这样也就有机会对某些字段建立索引,实现关系数据库的某些功能。

key-value 存储

Tokyo Cabinet / Tyrant

Berkeley DB

MemcacheDB

Redis

可以通过 key 快速查询到其 value。一般来说,存储不管 value 的格式,照单全收。(Redis 包含了其他功能)

图存储

Neo4J

FlockDB

图形关系的最佳存储。使用传统关系数据库来解决的话性能低下,而且设计使用不方便。

对象存储

db4o

Versant

通过类似面向对象语言的语法操作数据库,通过对象的方式存取数据。

xml数据库

Berkeley DB XML

BaseX

高效的存储 XML 数据,并支持 XML 的内部查询语法,比如 XQuery、Xpath。

2. Redis 简介

2.1 Redis 的起源

在 Redis 还没来到这个世界上的时候,MySQL 过得很辛苦,互联网发展得越来越快,它容纳的数据也越来越多,用户请求也随之暴涨,而每一个用户请求都变成了对它的一个又一个读写操作,MySQL 是苦不堪言。尤其是到了“双11”、“618“这种全民购物狂欢的日子,请求数量一上来,MySQL 很容易就奔溃的了。

据后来 MySQL 告诉 Redis 说,其实有一大半的用户请求都是读操作,而且经常都是重复查询一个东西,浪费它很多时间去进行磁盘 I/O。

后来有人就琢磨,是不是可以学学 CPU,给数据库也加一个缓存呢?于是 Redis 就诞生了!

出生不久, Redis 就和 MySQL 成为了好朋友, Redis 们俩常常携手出现在后端服务器中。

应用程序们把从 MySQL 查询到的数据,在 Redis 这里登记一下,后面再需要用到的时候,就先找 Redis 要, Redis 没有才再找 MySQL 要。

因为 Redis 把登记的数据都记录在内存中,不用去执行慢如蜗牛的 I/O 操作,所以找 Redis 要比找 MySQL 要省去了不少的时间呢。

可别小瞧这简单的一个改变, Redis 可为 MySQL 减轻了不小的负担!随着程序的运行, Redis 缓存的数据越来越多,有相当部分时间 Redis 都给它挡住了用户请求,这一下它可乐得清闲自在了!

有了 Redis 的加入,系统性能提升了不少,这都归功于 Redis 为数据库挨了不少枪子儿。

2.2 缓存过期 & 缓存淘汰

不过很快 Redis 发现事情不妙了, Redis 缓存的数据都是在内存中,可是就算是在服务器上,内存的空间资源还是很有限的,不能无节制地这么存下去, Redis 得想个办法,不然吃枣药丸。

不久, Redis 想到了一个办法:给缓存内容设置一个超时时间,具体设置多长交给应用程序们去设置, Redis 要做的就是把过期了的内容从 Redis 里面删除掉,及时腾出空间就行了。

超时时间有了, Redis 该在什么时候去干这个清理的活呢?

最简单的就是定期删除, Redis 决定 100ms 就做一次,一秒钟就是 10 次!

Redis 清理的时候也不能一口气把所有过期的都给删除掉, Redis 这里面存了大量的数据,要全面扫一遍的话那不知道要花多久时间,会严重影响 Redis 接待新的客户请求的!

时间紧任务重, Redis 只好随机选择一部分来清理,能缓解内存压力就行了。

就这样过了一段日子, Redis 发现有些个键值运气比较好,每次都没有被随机算法选中,每次都能幸免于难,这可不行,这些长时间过期的数据一直霸占着不少的内存空间!气抖冷!

Redis 眼里可揉不得沙子!于是在原来定期删除的基础上,又加了一招:

那些原来逃脱随机选择算法的键值,一旦遇到查询请求,被 Redis 发现已经超期了,那就绝不客气,立即删除。

这种方式因为是被动式触发的,不查询就不会发生,所以也叫惰性删除

可是,还是有部分键值,既逃脱了随机选择算法,又一直没有被查询,导致它们一直逍遥法外!而与此同时,可以使用的内存空间却越来越少。

而且就算退一步讲, Redis 能够把过期的数据都删除掉,那万一过期时间设置得很长,还没等到去清理,内存就吃满了,一样要吃枣药丸,所以还得想个办法。

Redis 苦思良久,终于憋出了个大招:内存淘汰策略,这一次要彻底解决问题!

Redis 提供了 8 种策略供应用程序选择,用于 Redis 遇到内存不足时该如何决策:

  • noeviction:返回错误,不会删除任何键值
  • allkeys-lru:使用 LRU 算法删除最近最少使用的键值
  • volatile-lru:使用 LRU 算法从设置了过期时间的键集合中删除最近最少使用的键值
  • allkeys-random:从所有 key 随机删除
  • volatile-random:从设置了过期时间的键的集合中随机删除
  • volatile-ttl:从设置了过期时间的键中删除剩余时间最短的键
  • volatile-lfu:从配置了过期时间的键中删除使用频率最少的键
  • allkeys-lfu:从所有键中删除使用频率最少的键

有了上面几套组合拳,我再也不用担心过期数据多了把空间撑满的问题了。

3. 缓存异常

系统引入了缓存层,那么就会面临三种缓存异常问题:缓存穿透、击穿和雪崩。

1)缓存穿透

Redis 的日子过得还挺舒坦,不过 MySQL 大哥就没 Redis 这么舒坦了,有时候遇到些烦人的请求,查询的数据不存在, MySQL 就要白忙活一场!不仅如此,因为不存在, Redis 也没法缓存啊,导致同样的请求来了每次都要去让 MySQL 白忙活一场。Redis 作为缓存的价值就没得到体现啦!这就是人们常说的缓存穿透

缓存穿透的发生一般有这两种原因:

  • 业务误操作:缓存中的数据和数据库中的数据都被误删除了,所以导致缓存和数据库中都没有数据。

  • 黑客恶意攻击:故意大量访问某些读取不存在数据的业务。

这一来二去,MySQL 大哥忍不住了:“唉,兄弟,能不能帮忙想个办法,把那些明知道不会有结果的查询请求给我挡一下。”

应对缓存穿透的方案,常见的方案有三种:

  1. 非法请求的限制。

  2. 缓存空值或者默认值。

  3. 使用布隆过滤器快速判断数据是否存在,避免通过查询数据库来判断数据是否存在。

第一种方案:非法请求的限制

当有大量恶意请求访问不存在的数据的时候,也会发生缓存穿透,因此在 API 入口处我们要判断求请求参数是否合理,请求参数是否含有非法值、请求字段是否存在,如果判断出是恶意请求就直接返回错误,避免进一步访问缓存和数据库。

第二种方案:缓存空值或者默认值

当我们线上业务发现缓存穿透的现象时,可以针对查询的数据,在缓存中设置一个空值或者默认值,这样后续请求就可以从缓存中读取到空值或者默认值,返回给应用,而不会继续查询数据库。

第三种方案:使用布隆过滤器快速判断数据是否存在,避免通过查询数据库来判断数据是否存在

布隆过滤器这位朋友别的本事没有,就擅长从超大的数据集中快速告诉你查找的数据存不存在(悄悄告诉你,我的这位朋友有一点不靠谱,它告诉你存在的话不能全信,其实有可能是不存在的,不过它要是告诉你不存在的话,那就一定不存在)。

即使发生了缓存穿透,大量请求只会查询 Redis 和布隆过滤器,而不会查询数据库,保证了数据库能正常运行,Redis 自身也是支持布隆过滤器的。

Redis 把这位朋友介绍给了应用程序,不存在的数据就不必去叨扰 MySQL 了,轻松帮忙解决了缓存穿透的问题。

2)缓存击穿

这之后过了一段时间太平日子,直到那一天···

有一次, MySQL 那家伙正优哉游哉地摸鱼,突然一大堆请求给他怼了过去,打了他一个措手不及。

一阵忙活之后, MySQL 怒气冲冲地找到了 Redis,“兄弟,咋回事啊,怎么一下子来得这么猛?”

Redis 查看了日志,赶紧解释到:“大哥,实在不好意思,刚刚有一个热点数据到了过期时间,被 Redis 删掉了,不巧的是随后就有对这个数据的大量查询请求来了,Redis 这里已经删了,所以请求都发到你那里去了。”

“你这干的叫啥事,下次注意点啊。” MySQL 大哥一脸不高兴地离开了。

我们的业务通常会有几个数据会被频繁地访问,比如秒杀活动,这类被频地访问的数据被称为热点数据

如果缓存中的某个热点数据过期了,此时大量的请求访问了该热点数据,就无法从缓存中读取,直接访问数据库,数据库很容易就被高并发的请求冲垮,这就是缓存击穿的问题。

缓存击穿跟下文的缓存雪崩很相似,可以认为缓存击穿是缓存雪崩的一个子集。

应对缓存击穿可以采取缓存雪崩中说到的两种方案:

  1. 互斥锁方案,保证同一时间只有一个业务线程更新缓存,未能获取互斥锁的请求,要么等待锁释放后重新读取缓存,要么就返回空值或者默认值。

  2. 不给热点数据设置过期时间,由后台异步更新缓存,或者在热点数据准备要过期前,提前通知后台线程更新缓存以及重新设置过期时间。

3)缓存雪崩

某一天,又出现了大量的网络请求发到了 MySQL 那边,比上一次的规模大得多, MySQL 大哥一会儿功夫就给干趴下了好几次!

等了好半天这一波流量才算过去,MySQL 才缓过神来。

“老弟,这一次又是什么原因?”MySQL 大哥累得没了力气。

“这一次比上一次更不巧,这一次是一大批数据几乎同时过了有效期,然后又发生了很多对这些数据的请求,所以比起上一次这规模更大了。”

MySQL 大哥听了眉头一皱:“那你倒是想个办法啊,三天两头折磨我,这谁顶得住啊?”

“其实我也很无奈,这个时间也不是我设置的,要不我去找应用程序说说,让他把缓存过期时间设置得均匀一些?至少别让大量数据集体失效。”

“走,咱俩一起去!”

后来,Redis 和 MySQL 俩去找应用程序商量了,不仅把键值的过期时间随机了一下,还设置了热点数据永不过期,这个问题缓解了不少。

Redis 和 MySQL 俩终于又过上了舒适的日子···

通常我们为了保证缓存中的数据与数据库中的数据一致性,会给 Redis 里的数据设置过期时间,当缓存数据过期后,用户访问的数据如果不在缓存里,业务系统需要重新生成缓存,因此就会访问数据库,并将数据更新到 Redis 里,这样后续请求都可以直接命中缓存。

那么,当大量缓存数据在同一时间过期(失效)或者 Redis 故障宕机时,如果此时有大量的用户请求,都无法在 Redis 中处理,于是全部请求都直接访问数据库,从而导致数据库的压力骤增,严重的会造成数据库宕机,从而形成一系列连锁反应,造成整个系统崩溃,这就是缓存雪崩的问题。

可以看到,发生缓存雪崩有两个原因:

  • 大量数据同时过期;

  • Redis 故障宕机;

不同的诱因,应对的策略也会不同。

大量数据同时过期

针对大量数据同时过期而引发的缓存雪崩问题,常见的应对方法有下面这几种:

  • 均匀设置过期时间

  • 互斥锁

  • 双 key 策略

  • 后台更新缓存

1. 均匀设置过期时间

如果要给缓存数据设置过期时间,应该避免将大量的数据设置成同一个过期时间。我们可以在对缓存数据设置过期时间时,给这些数据的过期时间加上一个随机数,这样就保证数据不会在同一时间过期。

2. 互斥锁

当业务线程在处理用户请求时,如果发现访问的数据不在 Redis 里,就加个互斥锁,保证同一时间内只有一个请求来构建缓存(从数据库读取数据,再将数据更新到 Redis 里),当缓存构建完成后,再释放锁。未能获取互斥锁的请求,要么等待锁释放后重新读取缓存,要么就返回空值或者默认值。

实现互斥锁的时候,最好设置超时时间,不然第一个请求拿到了锁,然后这个请求发生了某种意外而一直阻塞,一直不释放锁,这时其他请求也一直拿不到锁,整个系统就会出现无响应的现象。

3. 双 key 策略

我们对缓存数据可以使用两个 key,一个是主 key,会设置过期时间,一个是备 key,不会设置过期,它们只是 key 不一样,但是 value 值是一样的,相当于给缓存数据做了个副本。

当业务线程访问不到「主 key 」的缓存数据时,就直接返回「备 key 」的缓存数据,然后在更新缓存的时候,同时更新「主 key 」和「备 key 」的数据。

4. 后台更新缓存

业务线程不再负责更新缓存,缓存也不设置有效期,而是让缓存“永久有效”,并将更新缓存的工作交由后台线程定时更新

事实上,缓存数据不设置有效期,并不是意味着数据一直能在内存里,因为当系统内存紧张的时候,有些缓存数据会被“淘汰”,而在缓存被“淘汰”到下一次后台定时更新缓存的这段时间内,业务线程读取缓存失败就返回空值,业务的视角就以为是数据丢失了。

解决上面的问题的方式有两种:

第一种方式:后台线程不仅负责定时更新缓存,而且也负责频繁地检测缓存是否有效,检测到缓存失效了,原因可能是系统紧张而被淘汰的,于是就要马上从数据库读取数据,并更新到缓存。

这种方式的检测时间间隔不能太长,太长也导致用户获取的数据是一个空值而不是真正的数据,所以检测的间隔最好是毫秒级的,但是总归是有个间隔时间,用户体验一般。

第二种方式:在业务线程发现缓存数据失效后(缓存数据被淘汰),通过消息队列发送一条消息通知后台线程更新缓存,后台线程收到消息后,在更新缓存前可以判断缓存是否存在,存在就不执行更新缓存操作;不存在就读取数据库数据,并将数据加载到缓存。这种方式相比第一种方式缓存的更新会更及时,用户体验也比较好。

在业务刚上线的时候,我们最好提前把数据缓起来,而不是等待用户访问才来触发缓存构建,这就是所谓的缓存预热,后台更新缓存的机制刚好也适合干这个事情。

Redis 故障宕机

针对 Redis 故障宕机而引发的缓存雪崩问题,常见的应对方法有下面这几种:

  • 服务熔断或请求限流机制;

  • 构建 Redis 缓存高可靠集群;

1. 服务熔断或请求限流机制

因为 Redis 故障宕机而导致缓存雪崩问题时,我们可以启动服务熔断机制,暂停业务应用对缓存服务的访问,直接返回错误,不用再继续访问数据库,从而降低对数据库的访问压力,保证数据库系统的正常运行,然后等到 Redis 恢复正常后,再允许业务应用访问缓存服务。

服务熔断机制是保护数据库的正常允许,但是暂停了业务应用访问缓存服系统,全部业务都无法正常工作。

为了减少对业务的影响,我们可以启用请求限流机制,只将少部分请求发送到数据库进行处理,再多的请求就在入口直接拒绝服务,等到 Redis 恢复正常并把缓存预热完后,再解除请求限流的机制。

2. 构建 Redis 缓存高可靠集群

服务熔断或请求限流机制是缓存雪崩发生后的应对方案,我们最好通过主从节点的方式构建 Redis 缓存高可靠集群

如果 Redis 缓存的主节点故障宕机,从节点可以切换成为主节点,继续提供缓存服务,避免了由于 Redis 故障宕机而导致的缓存雪崩问题。

4)总结

缓存异常会面临的三个问题:缓存雪崩、击穿和穿透。

其中,缓存雪崩和缓存击穿主要原因是数据不在缓存中,而导致大量请求访问了数据库,数据库压力骤增,容易引发一系列连锁反应,导致系统奔溃。不过,一旦数据被重新加载回缓存,应用又可以从缓存快速读取数据,不再继续访问数据库,数据库的压力也会瞬间降下来。因此,缓存雪崩和缓存击穿应对的方案比较类似。

而缓存穿透主要原因是数据既不在缓存也不在数据库中。因此,缓存穿透与缓存雪崩、击穿应对的方案不太一样。

NoSQL & Redis 介绍、缓存穿透 & 击穿 & 雪崩的更多相关文章

  1. Redis中几个简单的概念:缓存穿透/击穿/雪崩,别再被吓唬了

    Redis中几个“看似”高大上的概念,经常有人提到,某些好事者喜欢死扣概念,实战没多少,嘴巴里冒出来的全是高大上的名词,个人一向鄙视概念党,呵呵! 其实这几个概念:缓存穿透/缓存击穿/缓存雪崩,有一个 ...

  2. redis的缓存穿透、击穿、雪崩以及实用解决方案

    今天来聊聊redis的缓存穿透.击穿.雪崩以及解决方案,其中解决方案包括类似于布隆过滤器这种网上一搜一大片但是实际生产部署有一定复杂度的,也有基于spring注解通过一行代码就能解决的,其中各有优劣, ...

  3. 深入了解Redis(7)-缓存穿透,雪崩,击穿

    redis作为一个内存数据库,在生产环境中使用会遇到许多问题,特别是像电商系统用来存储热点数据,容易出现缓存穿透,雪崩,击穿等问题.所以实际运用中需要做好前期处理工作. 一.缓存雪崩 1.概念 缓存雪 ...

  4. Redis 17 缓存穿透 缓存击穿 缓存雪崩

    参考源 https://www.bilibili.com/video/BV1S54y1R7SB?spm_id_from=333.999.0.0 版本 本文章基于 Redis 6.2.6 使用缓存的问题 ...

  5. redis:缓存穿透、缓存击穿、缓存雪崩

    缓存穿透的解决方案(空标记) 缓存穿透是指,在数据存储系统中不存在的记录,不会被存储到缓存中.这种记录每次的查询流量都会穿透到数据存储层.在高流量的场景下,不断查询空结果会大量消耗数据查询服务的资源, ...

  6. 缓存穿透、雪崩、热点与Redis

    (拼多多问:Redis雪崩解决办法) 导读:互联网系统中不可避免要大量用到缓存,在缓存的使用过程中,架构师需要注意哪些问题?本文以 Redis 为例,详细探讨了最关键的 3 个问题. 一.缓存穿透预防 ...

  7. 《Redis - 穿透/击穿/雪崩/集中失效》

    一:什么是缓存穿透? - 定义 - 正常情况下,我们在理想的条件下去查询缓存数据都是存在的. - 那么请求去查询一条数据库中不存在的数据,也就是缓存和数据库都查询不到这条数据. - 所以请求每次都会打 ...

  8. Redis缓存穿透和雪崩

    缓存穿透 用户想要查询一个数据 在redis缓存数据库中没有获取到 就会向后端的数据库中查询. 当用户很多 都去访问后端数据库的话,这就会给数据库带来很大的压力. 常见场景:秒杀活动 等 解决方法: ...

  9. Redis 穿透 & 击穿 & 雪崩

    原文:https://www.cnblogs.com/binghe001/p/13661381.html 缓存穿透 如果在请求数据时,在缓存层和数据库层都没有找到符合条件的数据,也就是说,在缓存层和数 ...

随机推荐

  1. 微信小程序:如何删除所有的console.log?

    使用vscode正则匹配,手动去除 1.用vscode打开微信小程序项目 2.Edit-----replace in Files 1. console.log()加了分号 console\.log\( ...

  2. 微信小程序:事件绑定

    小程序中绑定事件,通过bind关键字来实现.如bindinput,bindtap(绑定点击事件),bindchange等. 什么是事件 事件是视图层到逻辑层的通讯方式. 事件可以将用户的行为反馈到逻辑 ...

  3. Linux常用小命令

    1:查看当前磁盘内存 df-ah/df-hl 2:查看文件和文件夹大小 du -h --max-depth=1 /目的文件夹 3:scp 拷贝命令 指定端口传输文件 scp -p port filen ...

  4. Using Sqoop to import from db2 to hadoop

    参考 : https://stackoverflow.com/questions/23933481/db2-data-import-into-hadoop         sqoop import - ...

  5. Spirent Testcenter二层DHCP绑定流配置

    1.OLT配置 配一个VLAN,若GE口打Tag,不需要打PVID,打Untag,配PVID. 在ONU上配一个Other Bridge的WAN连接,并配置VLAN 2.TestCenter配置 选定 ...

  6. 基于url-to-pdf-api构建docker镜像,制作一个网页另存服务

    基于url-to-pdf-api构建docker镜像,制作一个网页另存服务 业务背景: 需要根据一个url路径打印这个网页的内容 解决方案: 1.使用wkhtml2pdf 2.使用puppeteer ...

  7. 1.3 PHP+MYSQL+APACHE配置(序)

    本节对服务器端web服务进行配置.事实上,对于配置这个环境(WAMP)网上还是有很多教程的,大家可以通过网上的教程完成配置,也不必拘泥于本文.甚至网上有免费的服务器端软件可以选择,比如著名的phpst ...

  8. Tango with django 1.9 中文——2.准备工作

    在正式开始写代码之前,设置好开发环境是非常重要的.你要确保所有必须的组件都已安装好.本章将概述五个你需要了解的关键组件的设置和使用.清单如下: 使用命令行 Python Python包管理器pip和虚 ...

  9. 痞子衡嵌入式:盘点国内RISC-V内核MCU厂商(2020年发布产品)

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是国内RISC-V内核MCU厂商(2020). 虽然RISC-V风潮已经吹了好几年,但2019年才是其真正进入主流市场的元年,最近国内大量 ...

  10. 如何掌握 C 语言的一大利器——指针?

    一览:初学 C 语言时,大家肯定都被指针这个概念折磨过,一会指向这里.一会指向那里,最后把自己给指晕了.本文从一些基本的概念开始介绍指针的基本使用. 内存 考虑到初学 C 语言时,大家可能对计算机的组 ...