redis锁处理并发_redis并发锁incr

2024-11-10 09:55 - 立有生活网

redis可以默认并发多少

redis并发问题

redis锁处理并发_redis并发锁incrredis锁处理并发_redis并发锁incr


redis锁处理并发_redis并发锁incr


redis中的并发问题

使用redis作为缓存已经很久了,redis是以单进程的形式运行的,命令是一个接着一个执行的,一直以为不会存在并发的问题,直到今天看到相关的资料,才恍然大悟~~

具体问题实例

linkA get myNum => 1linkB get myNum => 1linkA set muNum => 2linkB set myNum => 2

执行完作之后,结果可能是2,这和我们预期的3不一致。

再看一个具体的例子:

'scheme' => 'tcp',

'host' => '127.0.0.1',

'port' => 6379,]);for ($i = 0; $i < 1000; $i++) { $num = intval($client->get("name"));

$num = $num + 1;

$client->setex("name", $num, 10080);

usleep(1000本文包含有关需要绑定 时钟漂移 的类似系统的更多信息: Leases:分布式文件缓存一致性的高效容错机制 。0);}

设置name初始值为0,然后同时用两个终端执行上面的程序,name的值可能不是2000,而是一个<2000的值,这也就证明了我们上面的并发问题的存在,这个该怎么解决呢?

redis中的事务

redis中也是有事务的,不过这个事务没有mysql中的完善,只保证了一致性和隔离性,不满足原子性和持久性。

原子性,redis会将事务中的所有命令执行一遍,哪怕是中间有执行失败也不会回滚。kill信号、宿主机宕机等导致事务执行失败,redis也不会进行重试或者回滚。

持久性,redis事务的持久性依赖于redis所使用的持久化模式,遗憾的是各种持久化模式也都不是持久化的。

一致性,看了文档,觉得挺扯的,但是貌似说的没有问题。

redis中的事务不支持原子性,所以解决不了上面的问题。

当然了redis还有一个watch命令,这个命令可以解决这个问题,看下面的例子,对一个键执行watch,然后执行事务,由于watch的存在,他会监测键a,当a被修该之后,后面的事务就会执行失败,这就确保了多个连接同时来了,都监测着a,只有一个能执行成功,其他都返回失败。

127.0.0.1:6379> set a 1OK127.0.0.1:6379> watch aOK127.0.0.1:6379> multi OK127.0.0.1:6379> incr aQUEUED127.0.0.1:6379> exec1) (integer) 2

127.0.0.1:6379> get a"2"

失败时候的例子,从可以看出,test的值被其他连接修改了:

127.0.0.1:6379> set test 1OK127.0.0.1:6379> watch testOK127.0.0.1:6379> multiOK127.0.0.1:6379> incrby test 11QUEUED127.0.0.1:6379> exec(nil)

我的问题如何解决

redis中命令是满足原子性的,因此在值为数字的时候,我可以将ge加州大学伯克利分校的 Eric Brewer 在 ACM PODC 会议上提出 CAP 猜想。2年后,麻省理工学院的 S Gilbert 和 Nancy Lynch 从理论上证明了 CAP。之后,CAP 理论正式成为分布式计算领域的公认定理。t和set命令修改为incr或者incrby来解决这个问题,下面的代码开启两个终端同时执行,得到的结果是满足我们预期的2000。

'scheme' => 'tcp',

'host' => '127.0.0.1',

'port' => 6379,]);for ($i = 0; $i < 1000; $i++) { $client->incr("name");

$client->expire("name", 10800);

usleep(10000);}

评论中manzilu提到的方法查了下资料,确实可行,效果还不错,这里写了个例子

'scheme' => 'tcp',

'host' => '127.0.0.1',

'port' => 6379,]);class RedisLock{ public $objRedis = null;

public $timeout = 3;

/ @desc 设置redis实例 @param obj object | redis实例 /

public function __construct($obj)

public function getLockCacheKey($key)

} / @desc 获取锁 @param key string | 要上锁的键名 @param timeout int | 上锁时间 /

public function getLock($key, $timeout = NULL)

{ $timeout = $timeout ? $timeout : $this->timeout;

$expireAt = time() + $timeout;

$isGet = (bool)$this->objRedis->setnx($lockCacheKey, $expireAt);

if ($isGet) { return $expireAt;

} while (1) { usleep(10);

$time = time();

$oldExpire = $this->objRedis->get($lockCacheKey);

if ($oldExpire >= $time) { continue;

} $newExpire = $time + $timeout;

$expireAt = $this->objRedis->getset($lockCacheKey, $newExpire);

if ($oldExpire != $expireAt) { continue;

break;

} return $isGet;

} / @desc 释放锁 @param key string | 加锁的字段 @param newExpire int | 加锁的截止时间 @return bool | 是否释放成功 /

public function releaseLock($key, $newExpire)

{ $lockCacheKey = $this->getLockCacheKey($key);

if ($newExpire >= time()) { return $this->objRedis->del($lockCacheKey);

} return true;

}}$start_time = microtime(true);$lock = new RedisLock($client);$key = "name";for ($i = 0; $i < 10000; $i++) { $newExpire = $lock->getLock($key);

$num = $client->get($key);

$num++;

$client->set($key, $num);

$lock->releaseLock($key, $newExpire);}$end_time = microtime(true);echo "花费时间 : ". ($end_time - $start_time) . "n";

执行shell php setnx.php & php setnx.php&,会得到结果:

$ 花费时间 : 4.3004920482635

[2] + 56 done php setnx.php# root @ ritoyan-virtual-pc in ~/PHP/redis-high-concurrency [20:23:41] $ 花费时间 : 4.4319710731506

同样循环1w次,去掉usleep,使用incr直接进行增加,耗时在2s左右。

而获取所得时候取消usleep,时间不但没减少,反而增加了,这个usleep的设置要合理,免得进程做无用的循环

总结

看了这么多,简单的总结下,其实redis本事是不会存在并发问题的,因为他是单进程的,再多的command都是one by one执行的。我们使用的时候,可能会出现并发问题,比如get和set这一对。

2016-8-22 20:31:30

Redis的IO多路复用——单线程的理解(Redis6.0之后的多线程)

首先redis数据库的功能强大一些,因为不像memcached只支持保存字符串,redis支持string, list, set,sorted set,hash table 5种数据结构。例如存储一个人的信息就可以使用hash table,用人的名字做key,然后name super, age 24, 通过key 和 name,就可以取到名字super,或者通过key和age,就可以取到年龄24。这样,当只需要取得age的时候,不需要把人的整个信息取回来,然后从里面找age,直接获取age即可,高效方便。

Reactor 设计模式是一种 驱动 的设计模式,分发器(Dispatcher)使用多路分配器(Demultiplexer)多个客户端请求,当请求(Events)发生,分发器(Dispatcher)将一个或者多个客户端请求(Events)分发到不同的处理器(Event Handler)上,提升处理的效率。

下图为Reactor设计模式类图:

基于Reactor设计模式实现的IO多路复用

IO多路复用技术架构图如下

注:

多线程处理可能涉及锁,并且涉及切换线程的消耗。

耗时的命令会导致性能下@manzilu 提到的方法降,而且无法发挥CPU多核的性能。

Redis多线程只用来处理网络数据的读写和协议解析,命令的执行仍旧是单线程。这样的设计改变是为了不想让Redis因为引入多线程变得复杂。而且过去单线程的使用主要考虑CPU不是Redis的瓶颈,不需要多条线程并发执行,所以多线程模型带来的性能提升不能抵消它带来的开发和维护成本。

而现在引入多线程模型解决的是网络IO作的性能瓶颈。对于Redis基于内存的作,仍然是很快的,而有时IO作阻塞会影响着之后作的效率。改为多线程并发进行IO作,然后交由主线程进行内存作,这样可以更好的缓解IO作带来的性能瓶颈。

架构如下图:

redis是个单线程的程序,为什么会这么快呢?

{ return "lock_{$key}";

redis数据库实现原理

为了实现这些数据结构,redis定义了抽象的对象redis object,如下图。每一个对象有类型,一共5种:字符串,链表,,有序,哈希表。 同时,为了提高效率,redis为每种类型准备了多种实现方式,根据特定的场景来选择合适的实现方式,encoding就是表示对象的实现方式的。然后还有记录了对象的lru,即上次被访问的时间,同时在redis 中会记录一个当前的时间(近似值,因为这个时间只是每隔一定时间,进行自动维护的时候才更新),它们两个只就可以计算出对象多久没有被访问了。 然后redis object中还有引用计数,这是为了共享对象,然后确定对象的删除时间用的。使用一个void指针来指向对象的真正内容。正式由于使用了抽象redis object,使得数据库作数据时方便很多,全部统一使用redis object对象即可,需要区分对象类型的时候,再根据type来判断。而且正式由于采用了这种面向对象的方法,让redis的代码看起来很像c++代码,其实全是用c写的。

说到底redis还是一个key-value的数据库,不管它支持多少种数据结构,最终存储的还是以key-value的方式,只不过value可以是链表,set,sorted set,hash table等。和memcached一样,所有的key都是string,而set,sorted set,hash table等具体存储的时候也用到了string。 而c没有现成的string,所以redis的首要任务就是实现一个string,取名叫sds( dynamic string),如下的代码, 非常简单的一个结构体,len存储改string的内存总长度,free表示还有多少字节没有使用,而buf存储具体的数据,显然len-free就是目前字符串的长度。

字符串解决了,所有的key都存成sds就行了,那么key和value怎么关联呢?key-value的格式在脚本语言中很

哈希表的具体实现是和mc类似的做法,也是使用开链法来解决冲突,不过里面用到了一些小技巧。比如使用dictType存储函数指针,可以动态配置桶里面元素的作方法。又比如dictht中保存的sizemask取size(桶的数量)-1,用它与key做&作来代替取余运算,加快速度等等。总的来看,dict里面有两个哈希表,每个哈希表的桶里面存储dictEntry链表,dictEntry存储具体的key和value。

前面说过,一个dict对于两个dictht,是为了扩容(其实还有缩容)。正常的时候,dict只使用dictht[0],当dict[0]中已有entry的数量与桶的数量达到一定的比例后,就会触发扩容和缩容作,我们统称为rehash,这时,为dictht[1]申请rehash后的大小的内存,然后把dictht[0]里的数据往dictht[1]里面移动,并用rehashidx记录当前已经移动万的桶的数量,当所有桶都移完后,rehash完成,这时将dictht[1]变成dictht[0], 将原来的dictht[0]变成dictht[1],并变为null即可。不同于memcached,这里不用开一个后台线程来做,而是就在nt loop中完成,并且rehash不是一次性完成,而是分成多次,每次用户作dict之前,redis移动一个桶的数据,直到rehash完成。这样就把移动分成多个小移动完成,把rehash的时间开销均分到用户每个作上,这样避免了用户一个请求导致rehash的时候,需要等待很长时间,直到rehash完成才有返回的情况。不过在rehash期间,每个作都变慢了点,而且用户还不知道redis在他的请求中间添加了移动数据的作,感觉redis太了。

目前想到的原因有这几方面。

Libnt。和Memcached不同,Redis并没有选择libnt。Libnt为了迎合通用性造成代码庞大(目前Redis代码还不到libnt的1/3)及牺牲了在特定平台的不少性能。Redis用libnt中两个文件修改实现了自己的epoll nt loop(4)。业界不少开发者也建议Redis使用另外一个libnt高性能替代libev,但是作者还是坚持Redis应该小巧并去依赖的思路。一个印象深刻的细节是编译Redis之前并不需要执行./configure。

CAS问题。CAS是Memcached中比较方便的一种防止竞争修改资源的方法。CAS实现需要为每个cache key设置一个隐藏的cas token,cas相当value版本号,每次set会token需要递增,因此带来CPU和内存的双重开销,虽然这些开销很小,但是到单机10G+ cache以及QPS上万之后这些开销就会给双方相对带来一些细微性能别。

单线程有时候比多线程 或多进程更快,比需要考虑并发、锁,也不会增加上下文切换等开销,也即代码更加简洁,执行效率更高~~~

Redis是用内部是快的原因之一,要比较的化,可以同MemCache比较下。

分布式锁是什么

INSERT INTO mod_lock (mod_name, desc) VALUES ('modName', '测试的modName');

什么是分布式锁?实现分布式锁的三种方式

在很多场景中,我们为了保证数据的最终一致性,需要很多的技术方案来支持,比如分布式事务、分布式锁等。那具体什么是分布式锁,分布式锁应用在哪些业务场景、如何来实现分布式锁呢?

我们在开发应用的时候,如果需要对某一个共享变量进行多线程同步访问的时候,可以使用我们学到的锁进行处理,并且可以完美的运行,毫无Bug!

二、分布式锁应该具备哪些条件

在分析分布式锁的三种实现方式之前,先了解一下分布式锁应该具备哪些条件:

三、分布式锁的三种实现方式

目前几乎很多大型网站及应用都是分布式部署的,分布式场景中的数据一致性问题一直是一个比较重要的话题。分布式的CAP理论告诉我们“任何一个分布式系统都无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),最多只能同时满足两项。”所以,很多系统在设计之初就要对这三者做出取舍。在互联网领域的绝大多数的场景中,都需要牺牲强一致性来换取系统的高可用性,系统往往只需要保证“最终一致性”,只要这个最终时间是在用户可以接受的范围内即可。

基于数据库实现分布式锁;

四、基于数据库的实现方式

基于数据库的实现方式的核心思想是:在数据库中创建一个表,表中包含方法名等字段,并在方法名字段上创建索引,想要执行某个方法,就使用这个方法名向表中插入数据,成功插入则获取锁,执行完成后删除对应的行数据释放锁。

(1)创建一个表:

DROP TABLE IF EXISTS `mod_lock`;CREATE T} / @desc 获取锁键名 /ABLE `mod_lock` (

(2)想要执行某个方法,就使用这个方法名向表中插入数据:

因为我们对mod_name做了性约束,这里如果有多个请求同时提交到数据库的话,数据库会保证只有一个作可以成功,那么我们就可以认为作成功的那个线程获得了该方法的锁,可以执行方法体内容。

(3)成功插入则获取锁,执行完成后删除对应的行数据释放锁:

delete from mod_lock where mod_name ='modName';

注意:这只是使用基于数据库的一种方法,使用数据库实现分布式锁还有很多其他的!

五、基于Redis的实现方式

1、选用Redis实现分布式锁原因:

#连接redis

5、测试刚才实现的分布式锁

例子中使用50个线程模拟秒杀一个商品,使用–运算符来实现商品减少,从结果有序性就可以看出是否为加锁状态。

def seckill():

六、基于ZooKeeper的实现方式

七、总结

上面的三种实现方式,没有在所有场合都是完美的,所以,应根据不同的应用场景选择最适合的实现方式。

redis分布式锁用在事务里面什么意思

redis分布式锁用在ZooKeeper是一个为分布式应用提供一致的开源组件,它内部是一个分层的文件系统目录树结构,规定同一个目录下只能有一个文件名。基于ZooKeeper实现分布式锁的步骤如下:事务里面通常是为了保证同时对多个Redis资源进行原子化作,从而避免出现数据竞争等问题。

用Redis分布式锁,在事务中对资源加锁后,其他客户端尝试对这个资源进行作时,如果这个资源被锁定,则会等待一段时间后重试,这样可以保证作的顺序,并避免了多个客户端同时作同一个资源而导致的数据异常。

在使用R一 为什么要使用分布式锁edis分布式锁时,需要考虑锁的过期时间,以避免因某些异常情况导致锁无法被释放而影响系统正常运行。同时,必须确保任何情况下都要确保释放分布式锁,以便其他客户端能够正常获取锁并进行作。

阿里P8:《Netty、Redis、Zookeeper高并发实战》看完真不错

$lockCacheKey = $this->getLockCacheKey($key);

移动时代、5G时代、物联网时代的大幕已经开启,它们对于 高性能、高并发 的开发知识和技术的要求,抬升了 Ja工程师的学习台阶和面试门槛。

大公司的面试题从某个侧面映射出生产场景中对专项技术的要求。高并发的面试题以前基本是BAT等大公司的专利,现在几乎蔓延至与Ja项目相关的整个行业。例如,与 Ja NIO、Reactor模式、高性能通信、分布式锁、分布式ID、分布式缓存、高并发架构等 技术相关的面试题,从以前的加分题变成了现在的基础题,这也映射出开发Ja项目所必需的技术栈: 分布式Ja框架、Redis缓存、分布式搜索ElasticSearch、分布式协调ZooKeeper、消息队列Kafka、高性能通信框架Netty。

《Netty、Redis、Zookeeper高并发实战》为了让大家扎稳高性能基础,浅显易懂地剖析高并发IO的底层原理,细致细腻地解析Reacto而我们的系统最终满足的永远都是最终一致性,而这种最终一致性,有些时候有人会喜欢问关于分布式事务,而有些人则偏重在分布式锁上。r高性能模式,图文并茂地介绍Ja异步回调模式。掌握这些基础原理,能够帮助大家解决Ja后台开发的一些实际问题。

本书共12章,主要介绍高性能通信框架Netty,并详尽介绍Netty的EventLoop、Handler、Pipeline、ByteBuf、Decoder、Encoder等重要组件,然后介绍单体IM的实战设计和模块实现。本书对ZooKeeper、 Curator API、Redis、Jedis API的使用也进行详尽的介绍,让大家具备高并发、可扩展系统的设计和开发能力。

由于内容较多,本次将展示部分截图,如果看得不过瘾想更加深入地了解本笔记掌握,只需转发后私信回复【666】即可来获取免费领取方式了!

Redis怎么实现分布式锁

阿粉最近迷上了 Redis,为什么呢?感觉 Redis 确实功能很强大呀,一个基于内存的系统 Key{ $this->objRedis = $obj;-Value 存储的数据库,竟然有这么多的功能,而阿粉也要实实在在地把 Redis 来弄一下,毕竟面试的时候,Redis 可以说是一个非常不错的加分项。

为什么需要分布式锁?

目前很多的大型项目全部都是基于分布式的,而分布式场景中的数据一致性问题一直是一个不可忽视的问题,大家知道关于分布式的 CAP 理论么?

CAP 理论就是说任何一个分布式系统都无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),最多只能同时满足两项。

但是阿粉选择的就是使用缓存来实现分布式锁,也就是我们在项目中最经常使用的 Redis ,谈到 Redis,那真是可以用在太多地方了,比如说:

我们今天就来实现用 Redis 来实现分布式锁,并且要学会怎么使用。

1.准备使用 Jedis 的 jar 包,在项目中导入 jar 包。

jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime); 这个加锁的姿势才是我们最需要了解的,不然你用的时候都不知道怎么使用。

key:加锁的键,实际上就是相当于一个的标志位,不同的业务,你可以使用不同的标志位进行加锁。

requestId:这个东西实际上就是用来标识他是哪一个请求进行的加锁,因为在分布式锁中,我们要知道一件事,就是加锁的和解锁的,必须是同一个客户端才可以。

而且还有一种比较经典的就是 B 把 A 的锁给释放了,导致释放混乱,如果你不加相同的请求,A 线程处理业务,执行了加锁,锁的过期时间是5s, B线程尝试获取锁,如果 A 处理业务时间超过5s,这时候 A 就要开始释放锁,而B在这时候没有检测到这个锁,从而进行了加锁,这时候加锁的时候,A还没处理完对应业务,当他处理完了之后,再释放锁的话,要是就是直接把 B 刚加的锁释放了,要么就是压根都没办法释放锁。

SET_IF_NOT_EXIST:看字面意思,如果 key 不存在,我们进行Set作,如果存在,啥都不干,也就不在进行加锁。

SET_WITH_EXPIRE_TIME:是否过期

expireTime:这是给 key 设置一个过期的时间,万一你这业务一直被锁着了,然后之后的业务想加锁,你直接给一直持有这个这个锁,不进行过期之后的释放,那岂不是要凉了。

上面的方法中 tryGetDistributedLock 这个方法也就是我们通常使用的加锁的方法。

大家看到这个 script 的时候,会感觉有点奇怪,实际上他就是一个 Lua 的脚本,而 Lua 脚本的意思也比较简单。

其实这时候就有些人说,直接 del 删除不行么?你试试你如果这么写的话,你们的会不会把你的腿给你打断。

这种不先判断锁的拥有者而直接解锁的方式,会导致任何客户端都可以随时进行解锁,也就是说,这锁就算不是我加的,我都能开,这怎么能行呢?

在这里给大家放一段使用的代码,比较简单,但是可以直接用到你们的项目当中

分布式CAP理论:

也就是说,在二十年前的时候,CAP 理论只是个猜想。结果两年之后被证实了,于是,大家在考虑分布式的时候,就有根据来想了,不再是空想了。

什么是分布式的 CAP 理论 ?

一个分布式系统最多只能同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance)这三项中的两项

这个和(Atomicity)不太一样,因为之前看有些人说,在 CAP 理论中的 A 和数据库事务中的 A 是一样的,单词都不一样,那能一样么?

Availability :分布式中的 A 表示的是可用性,也就是说服务一直可用,而且是正常响应时间。

而你在搭建分布式系统的时候,要保证每个都是稳定的,不然你的可用性就没有得到相对应的保证,也谈不上是什么分布式了。只能称之为一个伪分布式。

Consistency: 一致性

也就是说你的更新作成功并返回客户端完成后,所有在同一时间的数据完全一致,这个如果你在使用 Redis 做数据展示的时候,很多面试官都会问你,那你上图可以看到,变量A存在三个内存中(这个变量A主要体现是在一个类中的一个成员变量,是一个有状态的对象),如果不加任何控制的话,变量A同时都会在分配一块内存,三个请求发过来同时对这个变量作,显然结果是不对的!即使不是同时发过来,三个请求分别作三个不同内存区域的数据,变量A之间不存在共享,也不具有可见性,处理的结果也是不对的!们是怎么保证数据库和缓存的一致性的呢?

毕竟你只是读取的话,没什么问题,但是设计到更新的时候,不管是先写数据库,再删除缓存;还是先删除缓存,再写库,都有可能出现数据不一致的情况。

所以如果你对这个很感兴趣,可以研究一下,比如说:

如果你能在面试的时候把这些都给面试官说清楚,至少感觉你应该能达到你自己的工资要求。

Partition tolerance:分区容错性

分布式系统在遇到某或网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务。

其实在 CAP 理论当中,我们是没有办法同时满足一致性、可用性和分区容错性这三个特性,所以有所取舍就可以了。

关于使用 Redis 分布式锁,大家学会了么?

redis 为什么需要分布式锁

值得强调的是,对于未能获取大多数锁的客户端来说,尽快释放(部分)获取的锁是多么重要,这样就不需要等待密钥到期才能再次获取锁(但是,如果发生网络分区并且客户端不再能够与 Redis 实例通信, 在等待密钥过期时需要支付可用性罚款)。

比如:秒杀,全局递增ID,楼层生成等等。

[1] + 55 done php setnx.php

大部分的解决方案是基于DB实现的,Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系。

其次Redis提供一些命令SETNX,GETSET,可以方便实现分布式锁机制。

redis多线程处理下,同时设置一个key的值

有个键,设名称为myNum,里面保存的是数字,设现在值为1,存在多个连接对myNum进行作的情况,这个时候就会有并发的问题。设有两个连接linkA和linkB,这两个连接都执行下面的作,取出myNum的值,+1,然后再存回去,看看下面的交互:

我的做法是,程序端控制资源访问,设置读写锁,更新就请求写锁,读锁是共享的,但是读锁与写锁是互斥的。更新必须按顺序更新,读取可以并发。这样肯定对。因为确认不了redis的线程安全性,自己实现线程安使用 延迟重启 ,即使没有任何可用的Redis持久性,基本上也可以实现安全,但请注意,这可能会转化为可用性损失。例如,如果大多数实例崩溃,系统将全局不可用 TTL (此处全局意味着在此期间根本没有资源可锁定)。全更保险。

在英语课上插英语课老师app 在英语课上插英语

关于在英语课上插英语课老师app,在英语课上插英语课老师播放这个很多人还不知道,今天小天来为大家解答以上的问题,现在让我们一起来看看吧! 在英语课上插英语课老师app 在英语课上插英语···

x战记尺度大吗_x战记是悲剧吗

谁告诉我点比较的帅男的动漫 无法逃离的背叛(重点),吸血骑士,金色琴弦(重点),天堂学院(重点),樱兰高校部,月桂树树下的王子(重点),网球王子,遥远的时空(重点),X战记,···

地铁2号线站点时间表查询_地铁2号线站点明细

南京地铁2号线运营时间表 为你找到南京地铁2号线的详细信息如下: 地铁2号线站点时间表查询_地铁2号线站点明细 地铁2号线站点时间表查询_地铁2号线站点明细 【往鱼嘴】(首班车时间:06:00,末···