普通视图

发现新文章,点击刷新页面。
昨天以前zguishen's blog

Redis 消息队列在 SpringBoot 中的使用

作者 zguishen
2024年4月27日 00:00

Redis 除了做数据缓存,做 NoSQL 数据库,也可以当做轻量级消息队列使用,并且提供了基于 List 实现的、基于 Pub/Sub 机制的订阅/发布模式、基于 sorted set 的实现和基于 Stream 类型的实现几种实现方式。其中 List 实现的分非阻塞和阻塞方式,Stream 则是 Redis 5 加入的消息队列。

之前代码已经写过了,只是工程整合搞得比较复杂,所以这里算是写份注释文档。

关联代码地址lin/lin-redis at master · zgshen/lin

使用 List 类型实现

List 就是列表数据结构,用来做消息队列这是最简单直观的了,也是典型的点对点消息模型,先看下 Redis 列表提供的操作命令。

push 压入:

  • LPUSH key value1 [value2 ...] 将一个或多个值插入到列表头部
  • RPUSH key value1 [value2 ...] 将一个或多个值插入到列表尾部

pop 弹出:

  • LPOP key 移除并获取列表的第一个元素
  • RPOP key 移除并获取列表的最后一个元素

阻塞弹出;:

  • BLPOP key1 [key2 ...] timeout 移除并获取列表的第一个元素,若列表为空则阻塞等待
  • BRPOP key1 [key2 ...] timeout 移除并获取列表的最后一个元素,若列表为空则阻塞等待

压入和弹出前面的 L 和 R 表示从队列左端和右端压入和弹出,阻塞弹出的 B 代表就是 blocking 的意思。

使用队列一般遵循先进先出,所以要么左近右出,要么右近左出,框架提供的 RedisTemplate 封装了 Redis 的操作命令,push 和 pop 直接调用就行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Autowired
private RedisTemplate redisTemplate;

//左进
public Long push(String... params) {
Long aLong = redisTemplate.opsForList().leftPushAll(LIST_PUSH_POP_MSG, params);
return aLong;
}

//右出,轮询检测
public String pop() {
String str = redisTemplate.opsForList().rightPop(LIST_PUSH_POP_MSG).toString();
return str;
}

再看下堵塞弹出的异步操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void blockingConsume() {
List<Object> obj = redisTemplate.executePipelined(new RedisCallback<Object>() {
// @Nullable
@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException {
//队列没有元素会阻塞操作,直到队列获取新的元素或超时
//return connection.bRPop(PUB_SUB_TIME_OUT, LIST_PUSH_POP_MSG.getBytes());
return connection.bLPop(PUB_SUB_TIME_OUT, LIST_PUSH_POP_MSG.getBytes());
}
}, new StringRedisSerializer());
for (Object str : obj) {
log.info("blockingConsume : {}", str);
}
}

此外 Redis 还有两个命令 RPOPLPUSH、BRPOPLPUSH(阻塞)可以从一队列获取队列并且写入另一个队列,可以用于简单保证消息可靠性,业务成功处理后再移除另一队列的消息,如果业务处理失败又可以从另一队列恢复。

1
2
3
4
5
6
7
8
9
10
11
12
13
public String rightPopLeftPush() {
String str;
try {
str = redisTemplate.opsForList().rightPopAndLeftPush(LIST_PUSH_POP_MSG, LIST_PUSH_POP_BACKUP_MSG).toString();
// 其他业务,处理失败了还能在 LIST_PUSH_POP_BACKUP_MSG 队列找到备份
} catch (Exception e) {
log.error("业务异常:{}", e.getMessage());
throw new RuntimeException(e);
}
// 先进先出业务完毕出栈,让异常的消息留在队列里
redisTemplate.opsForList().leftPop(LIST_PUSH_POP_BACKUP_MSG);
return str;
}

使用 Sorted Set 实现

Sorted Set 是有序集合,元素唯一不可重复,元素按照 score 值升序排列,支持范围操作,所以适合做简单的延迟消息队列。

添加元素:

  • ZADD key score member [score member ...] 向有序集合中加入一个或多个成员,或更新已存在成员的分数

获取元素:

  • ZRANGE key start stop [WITHSCORES] 按位置范围遍历集合,可附加分数
  • ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT...] 按分数范围遍历集合

以下是简单的生产和消费程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* @param businessId 业务 id(如订单 id 等)
* @param expiredTime 延时时间,单位秒
*/
public void produce(String businessId, long expiredTime) {
redisTemplate.opsForZSet().add(MsgConstant.SORTED_SET_MSG, businessId, System.currentTimeMillis() + expiredTime * 1000);
}

/**
* 简单的消费程序
* 死循环,仅做测试
*/
public void consume() {
while (true) {
//(K key, double min, double max, long offset, long count)
//键,要取区间score最小值,要取区间score最大值,偏移(从哪个位置开始),数量
Set<String> set = redisTemplate.opsForZSet().rangeByScore(MsgConstant.SORTED_SET_MSG, 0, System.currentTimeMillis(), 0, 1);
if (set == null || set.isEmpty()) continue;
log.info(set.toString());
String next = set.iterator().next();
Long remove = redisTemplate.opsForZSet().remove(MsgConstant.SORTED_SET_MSG, next);
if (remove > 0) log.info("{} remove success.", next);
}
}

使用 Pub/Sub 订阅发布模式

发布者把消息发到某个频道,订阅改频道的所有消费者都会收到消息,即消息多播,并且订阅支持模糊匹配频道。这种方式就是常规的消费者-消费者模型,不过与典型的 MQ 还是有区别,Pub/Sub 订阅发布更像是个广播,不能并发消费,不支持持久化,也没有 ACK 确认。

发布命令:

  • PUBLISH channel message : 将消息 message 发布到指定的频道 channel

订阅命令:

  • SUBSCRIBE channel [channel ...] : 订阅一个或多个频道
  • PSUBSCRIBE pattern [pattern ...] : 订阅一个或多个模式,用于模糊匹配频道

Spring 工程的配置类:

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
@Bean
public RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
MessageListenerAdapter adapter, MessageListenerAdapter adapter1) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
//主题的监听,adapter 和 adapter1 对应下面两个 bean 实例,有多少
container.addMessageListener(adapter, new PatternTopic(PUB_SUB_MSG));//普通的订阅者
container.addMessageListener(adapter1, new PatternTopic(PUB_SUB_MSG_FUZZY));//模糊匹配的订阅者
return container;
}

/**
* 多个订阅
* @param message
* @return
*/
@Bean
public MessageListenerAdapter adapter(MessageSubscribe message){
// MessageSubscribe 的 onMessage 监听获取订阅数据
return new MessageListenerAdapter(message, "onMessage");
}

@Bean
public MessageListenerAdapter adapter1(MessageSubscribe1 message){
// MessageSubscribe1 的 onMessage
return new MessageListenerAdapter(message, "onMessage");
}

订阅者类:

1
2
3
4
5
6
7
8
9
10
@Slf4j
@Component
public class MessageSubscribe implements MessageListener {

@Override
public void onMessage(Message message, byte[] bytes) {
log.info("sub, topic name: {}, message: {}", new String(bytes), new String(message.getBody()));
}

}

发布者类:

1
2
3
4
5
6
7
8
9
10
11
@Service
public class MessagePublish {

@Autowired
StringRedisTemplate redisTemplate;

public void publish(String channel, String msg) {
redisTemplate.convertAndSend(channel, msg);
}

}

使用 Stream

Redis 5.0 新增了 Stream 的数据结构,与 Pub/Sub 订阅发布模式相比,Redis Stream 提供了消息的持久化和主备复制功能。

添加消息:

1
XADD key ID field value [field value ...] 

其中ID,消息id,可使用 * 表示由 redis 生成,可以自定义,但是要自己保证递增性

读取消息:

1
XREAD [COUNT count] [BLOCK milliseconds] STREAMS key [key ...] id [id ...]

milliseconds 设置堵塞秒数,没设置就是非阻塞模式。

创建消费者组:

1
XGROUP [CREATE key groupname id-or-$] [SETID key groupname id-or-$] [DESTROY key groupname] [DELCONSUMER key groupname consumername]

key 队列名,不存在就创建;groupname 组名;$ 表示从尾部开始消费,只接受新消息,当前 Stream 消息会全部忽略。

1
2
3
4
5
# 从头开始消费
XGROUP CREATE mystream consumer-group-name 0-0

# 从尾部开始消费
XGROUP CREATE mystream consumer-group-name $

读取消费者组中的消息:

1
XREADGROUP GROUP group consumer [COUNT count] [BLOCK milliseconds] [NOACK] STREAMS key [key ...] ID [ID ...]

group 消费组名;consumer 消费者名;count 读取数量;milliseconds 阻塞毫秒数;key 队列名;ID 消息 ID。

例子:

1
XREADGROUP GROUP consumer-group-name consumer-name COUNT 1 STREAMS mystream >

看下在 Spring Boot 中的使用:

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
/**
* 启动初始化配置,注册 listener
*/
@Slf4j
@Component
public class RedisStreamRunner implements ApplicationRunner, DisposableBean {

private StreamMessageListenerContainer<String, MapRecord<String, String, String>> container;
private final ThreadPoolTaskExecutor executor;
private final RedisConnectionFactory redisConnectionFactory;
private final StringRedisTemplate stringRedisTemplate;

public RedisStreamRunner(ThreadPoolTaskExecutor executor, RedisConnectionFactory redisConnectionFactory, StringRedisTemplate stringRedisTemplate) {
this.executor = executor;
this.redisConnectionFactory = redisConnectionFactory;
this.stringRedisTemplate = stringRedisTemplate;
}

@Override
public void run(ApplicationArguments args) throws Exception {
StreamMessageListenerContainer.StreamMessageListenerContainerOptions<String, MapRecord<String, String, String>> options =
StreamMessageListenerContainer.StreamMessageListenerContainerOptions.builder()
.batchSize(10)// 一次性最多拉取多少条消息
.executor(executor)// 执行消息轮询的执行器
.pollTimeout(Duration.ZERO)// 超时时间,设置为0,表示不超时(超时后会抛出异常)
.build();

StreamMessageListenerContainer<String, MapRecord<String, String, String>> container =
StreamMessageListenerContainer.create(redisConnectionFactory, options);

initStreamAndGroup(stringRedisTemplate.opsForStream(), STREAM_KEY, STREAM_GROUP);
// receive 方法内部 autoAcknowledge 为 false,需要手动 ack 的
container.receive(Consumer.from(STREAM_GROUP, STREAM_CONSUMER), //消费组和消费者,这里只演示一个消费者
StreamOffset.create(STREAM_KEY, ReadOffset.lastConsumed()),//读取 id 大于消费者组最后消费的所有新到达元素
new TestStreamListener(stringRedisTemplate));//消费消息,业务处理

this.container = container;
this.container.start();
}

/**
* 消费组,不存在则创建
*/
private void initStreamAndGroup(StreamOperations<String, ?, ?> ops, String streamKey, String group) {
String status = "OK";
try {
StreamInfo.XInfoGroups groups = ops.groups(streamKey);
if (groups.stream().noneMatch(xInfoGroup -> group.equals(xInfoGroup.groupName()))) {
status = ops.createGroup(streamKey, group);
}
} catch (Exception exception) {
RecordId initialRecord = ops.add(ObjectRecord.create(streamKey, "Initial Record"));
Assert.notNull(initialRecord, "Cannot initialize stream with key '" + streamKey + "'");
status = ops.createGroup(streamKey, ReadOffset.from(initialRecord), group);
} finally {
Assert.isTrue("OK".equals(status), "Cannot create group with name '" + group + "'");
}
}

@Override
public void destroy() {
this.container.stop();
}

}

TestStreamListener 处理消息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Slf4j
public class TestStreamListener implements StreamListener<String, MapRecord<String, String, String>> {

StringRedisTemplate redisTemplate;

public TestStreamListener(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}

@Override
public void onMessage(MapRecord<String, String, String> message) {

log.info("MessageId: " + message.getId());
log.info("Stream: " + message.getStream());
log.info("Body: " + message.getValue());
//记得手动确认
redisTemplate.opsForStream().acknowledge(STREAM_GROUP, message);
}
}

生产者:

1
2
3
4
5
6
7
8
9
10
11
12
@Service
public class TestStreamProducer {

@Autowired
StringRedisTemplate redisTemplate;

//发送流信息
public void add(String streamKey, String msg) {
redisTemplate.opsForStream().add(Record.of(msg).withStreamKey(streamKey));
}

}

参考

你想活出怎样的人生 君たちはどう生きるか

作者 zguishen
2024年4月11日 00:00

《你想活出怎样的人生》片名英译 The Boy and the Heron,男孩与苍鹭。电影故事挺简单清晰的,但是片中很多隐喻大概得看几遍才看明白,这第一遍看完最深的印象居然是多邻国的多儿(片中有很多绿色鹦鹉,太像多儿了)。

这应该是宫崎骏最后的作品的,这部片看起来更像是宫崎骏在讲自己的故事,片中可以看到他以往的很多作品的影子,比如说跟《千与千寻》很像的神隐世界的海、汤屋、哇啦哇啦和纸人,《幽灵公主》的树精灵,《红猪》的亭子等等,同时明显感觉到宫崎骏想把很多题材一并讲完,有很多话想说,但是一部影片就这么长很难顾全。

故事背景时间在二战,战时民生凋敝物资紧缺,百姓流离失所,主角(真人)母亲死于空袭火灾,父亲是军工工厂老板,战争使得生意更好了,父亲再婚老婆是小姨子...

接下来是讲家庭,主角和继母关系并不怎么好,继母还是妈妈的妹妹。主角在学校打架被欺负为了不去学校故意砸伤自己的头在家养伤,在家看到母亲的遗物,一本书《你想活出怎样的人生》,对应片名。

然后就是进入高塔探索,神隐世界的实物有太多的隐喻说实话看不明白,比如说苍鹭、哇啦哇啦、鹈鹕、鹦鹉、消失在塔中的舅公、儿童时期的母亲等等。其中被困的鹈鹕吃不到鱼只能吃哇啦哇啦,而哇啦哇啦又是将要投胎的人的灵魂,洗手间旁将死的鹈鹕解释是没办法为了活下去才吃哇啦哇啦,这里是在影射日本战时的困境?或者是民众被拖入战火的困状,食物匮乏到以至于吃人(哇啦哇啦),与电影开头的人民生活镜头呼应?火美用烟火驱赶鹈鹕代表日本被轰炸;再者鹦鹉也是类似,鹦鹉甚至组建了国家,还有个很疯狂的大王。祖父用白色石头搭建稳定的积木才稳定神隐世界这是象征世界的稳定如同积木一般摇摇欲坠?疯狂的鹦鹉大王一到把积木劈掉导致神隐世界的崩溃,这里是在象征疯狂的军国主义?

我更偏向这是一场梦作为解释,通过与逝去的亲人的沟通化解矛盾,认识现实世界,让主角有勇气回到满目疮痍的现实。

综合看下来,这是宫崎骏一部回顾之作、总结之作。

每次写影评都乱糟糟的,特别这种意识流的不多看一两遍很多细节没法看到,有时闲累了干脆看别人的解析去了。

FAR:Lone Sails

作者 zguishen
2023年10月23日 00:00

I remember a different world. I am not alone.

最近在小黑盒看到这个打折的小游戏,这几年越来越喜欢玩这类小游戏,流程较短,制作精良,而且经常有让人眼前一亮的作品出现。另一个原因是3A大作实在是玩不动了,虽然说不是没时间没精力去玩,但是没有那种持续性地、几十个小时沉浸在一个游戏的耐心,没有继续玩下去的渴望,现在有人称这种现象为“电子阳痿”(当然也很多表现,这里说的只是一种)。

FAR: Lone Sails 以末日世界为背景,主角小红人独自穿越干涸的海洋,在途中获得一辆车,一路上找油桶给车加油,给车加上风帆、换轮子、加进气口,装备灭火器和修理工具。

跋山涉水,行驶在在漫天星辰的夜空下,在雷雨冰雹中,在银装的雪地中,这是一场孤独的旅行,一路上没碰到其他同类,有点《少女的终末旅行》中两位少女漫无目的地持续着旅行。

一路中会碰到各种建筑遗迹,最好玩的是还能进入一架类似星球大战中的全地形装甲步行机(AT-AT WALKER)的大型机器。

碰上了火山爆发,拼命加速逃跑...

可惜车子还是受损了,动力系统被毁,只能靠风力继续前进。

在终点将剩下的燃料点燃一摊篝火,结束这段旅程。

Panasonic LX10 & Panasonic ZS110

作者 zguishen
2023年10月18日 00:00

一般想买卡片机的人看中的都是便携性,虽然现在的微单都在尽量往便携方向设计,但加上镜头依旧不算非常轻便。卡片机兼顾便携和比一般手机更好的画质,不过现在旗舰手机的摄影也很强了,卡片机在画质也没明显的优势了,只是在操控和可玩性更强一些。综合来说,既要非常便携,随便能装进口袋随手就能拿出来,又想要相机更多的可玩性,卡片机才值得入手。

用过的两个松下的卡片机 LX10 和 ZS110,他们有相比索尼黑卡更便宜的价格,而且松下的操控系统也好用多了。这两款相机参数不同也各有优缺点。


LX10 F1.4-2.8 24-72mm


ZS110 F2.8-5.9 25-250mm

LX10的焦段太局限了,卡片机拍人其实都不咋滴,LX10 也只是比 ZS110 好一些,而且小巧便携就是为了出门方便拍景,明显焦段范围更大的 ZS110 更实用些。不过 LX10 的做工比较扎实小巧,翻转屏也好使;ZS110 相对 LX10 大一点点,没有翻转屏,多了一个取景器但是太小,基本是摆设,做工一般,前面板按键真的松垮。

香港

作者 zguishen
2023年10月16日 00:00

香港文化中心

国庆前本来就打算去一趟香港的,结果错过了中旅的巴士没去成。长假过后(10月11日)重新买票过去,这才发现中旅的车是类似公交车的运营方式,买好票后自己按时上车,没有人通知,也不需要确认车牌号的,就突出一个出行简单,不打扰人。

10月9日的时候有个台风“小犬”过境香港,这名字可爱,只是下的雨可不小,11日天气开始转好,台风天过后天气凉快,也有阳光,适合出行,所以就这天过来了。

路线:尖沙咀 - 中环海滨摩天轮 - 太平山卢吉道 - 湾仔 - 文化中心/艺术馆 - 星光大道

7:20从东圃上车,9:00就到深圳湾口岸处境香港了,再换乘香港的巴士直接到尖沙咀中港城刚好11:00。然后就在中行的 ATM 取钱,结果死后取不了,也不知道为啥,干脆就没取了,反正已经在 iPhone 上开好了八达通,也够在香港畅通无阻了。

作为一个对探索美食没那么大兴趣的人,午餐还是选择熟悉的麦门,我知道有些人旅行其实就是为了到处吃吃吃,确实品尝美食也是旅途的一部分意义。尖沙咀天星码头附近就有一个麦当劳,而且码头入口这里刚好还有一个领取“香港有礼”的地方,香港有礼其实就是旅客消费优惠券。

做天星小轮过维多利亚港,到达港岛中环,摩天轮打下卡。中环地铁站附近,穿过皇后大道(还记得那首皇后大道东吗),到达山顶缆车,刷八达通上太平山,上山62HKD下山26HKD。到达山顶广场走卢吉道,到达卢吉道观光点,可以看到维港全貌,可惜上山天气转阴天了,拍照没那么好看了。

卢吉道

下山到银行街,顾名思义就是有很多银行,本来想去办银行卡的,但是想到现在没工作想办应该很难,就放弃了。银行街做叮叮车到湾仔码头,坐船回尖沙咀。继续打卡香港文化中心和香港艺术馆,先去看的艺术馆,可惜时间还是不够了,艺术馆共五楼看到三楼就快六点了要闭馆了,没看完。艺术馆内容其实还是岭南传统和贸易城市的一类东西,跟广州十三行博物馆有些相似,展馆本身也是由广港澳三地的博物馆单位联合设计的。

叮叮车

天黑后就到维港看夜景吹海风去了,有趣的是一天中碰到好几个外国人要我帮他拍照,甚至还有是一个family十来个人的合照,,可能是因为我手上拿着相机,一个走在路上显得悠闲有时间才会被拜托帮忙拍照吧。只是我一直拍的是风光不拍人,真不会拍人,帮他们拍都是随手按下快门就完事了,英文不咋滴也没法跟他们沟通太多,我知道“good、awesome”都是礼貌而已的啦哈哈。

有人说香港已经不是以前的香港了,时代环境和政策的变动太多了,但是香港依旧还是大陆最开放最国际化的城市。关于对内地的偏见和语言的问题,由于我不怎么跟人交流,还没碰到这类现象,实在不行,干脆只说英文得了。

阳了

作者 zguishen
2022年12月29日 00:00

作为一个宅男,本身就很少出门,即使出门也是规规矩矩戴好口罩,但是还是感染 COVID-19 病毒了,最大的原因也许是拿快递或点外卖接触但又没有消毒,所以感染了。不过以这个病毒的传播能力来看,基本上是很难防得住了,感染难以避免。

  • 1217:下午开始高烧,体温升得很快,身体痛,特别是腰痛。吃泰诺然后睡了几个小时感觉好多了。
  • 1217:今天再吃一片泰诺已经不会烧了,但是喉咙开始痛了。
  • 1219:第三天。凌晨两点醒了,喉咙痛得要死,四点又醒了一次,六点再醒一次,真是折磨。现在是肋骨痛喉咙痛怕冷。
  • 1220:第四天,继续受折磨,喉咙吞刀子。
  • 1221:第五天,症状减轻很多了。

......

  • 1229:鼻塞,喉咙还是一直有点不舒服,偶尔咳嗽,看来是一时半会没法完全好的。

整个过程下来我只吃了两片泰诺(对乙酰氨基酚缓释片),然后就是每天两片维生素,其他药都没吃。初期高烧浑身痛很难受,说只是小号感冒纯骗人,比感冒难受多了,而且持续时间长,直到今天(19日)依然在咳嗽,精神状态也不好,人容易累。

《Stray》

作者 zguishen
2022年7月20日 00:00

stray截图

现在很多人独自在大城市打工,住出租屋,合租还好,一个人住的都很孤独,喜欢猫猫养猫猫的人越来越多。

今年错过了Steam夏促,没买游戏,就预购了一个猫猫游戏《Stray》

游戏卖得很好,游戏评价也是好评如潮,只要你给我猫猫,我就给你好评。谁不喜欢猫猫呢。

因生病将人类意识上传网络的B12

不过游戏真的很一般,流程短,我6个多小时通关,还碰到卡流程的恶性bug。游戏内容缝合元素很多:

  • 生化(也有点像怪奇物语中的颠倒世界)的恶心变异病毒和大眼睛很哈人;
  • 尼尔的原始聚落;
  • 赛博朋克城市;
  • 99围城像辐射避难所。

除去猫猫元素,这游戏我大概只能给个及格分数,猫猫加分太多了。不过,能搞出猫猫这种创新元素,给他好评是他应得的,其他游戏厂商怎么就没想到呢,以后有同类型题材的游戏,大家可能就不会这么宽容了。

现在大家都喜欢猫猫狗子,还有各种布偶玩具也有点这种感觉在里面,不会养猫狗的可以买个fufu或者买只宜家鲨鲨陪你写代码。

赛博朋克风格城市

2023-07-26 修改

火炉山——华快观光点

作者 zguishen
2023年7月13日 00:00

上周四(7月3日)到火炉山去转了一圈,下午35℃的天气,居然还有不少像我一样的傻瓜去爬山的。其实当时是想去华快观光点拍一个广州三件套,需要在天气好空气质量好的时候才拍得清晰一些,本来以为当天能见度已经很不错了,不过当爬到山上远看还是有些雾霾。

LX10最长只有72mm,真拍不了风光,想换长焦相机了。

华快-72mm

华快-裁剪

铃芽之旅 すずめの戸締まり

作者 zguishen
2023年7月3日 00:00

我想要活下去,想听见你的声音。

新海诚电影风格一直以来都是以精美的制作画面和青春男女的 相遇相知 相爱或相忘的故事吸引人的,从十几年前的《秒速五厘米》《言叶之庭》到近些年《你的名字。》《天气之子》,不仅能看到制作水平的进步,剧本的风格转变。尤其以《你的名字。》的成功,让新海诚这块招牌打得愈加响亮。不过,这次的《铃芽之旅》打破了人们以往对新海诚电影的固有印象,电影要表达的有更多的东西,但是也有很多批评,我个人总体来说《铃芽之旅》相比较《你的名字。》的表现差。

电影四月份上映的时候就去影院看了,一开始先入为主,以为是新海诚传统的电影风格,又一个男女爱情故事,可是越往后面看越觉得感情线好像不是那么重要。

故事开始是女主铃芽无意中拔出要石,引发灾厄,要石变成白猫(大臣),诅咒草太肉体消失附身在三腿小座椅上。随后草太为了抓住大臣变回人形,一路跟随大臣的逃跑路线开启了一边抓猫一边关闭沿途被打开的后门的旅程。总体看下来这部电影既像爱情片又像公路片,也可以是纪念地震的。事实上,我认为纪念地震才是电影的主题,这就导致我在影院看的时候觉得观感很差,各条主题线混杂在一起,但是新海诚明显没处理好。而且电影里细节很多但又不明显,需要对日本的地震史(东日本大震灾)有了解一些,白猫黑猫又代表什么,动机是什么,只看一遍很难理解,现在电影在流媒体上线了重新看了一遍,才看到了更多的细节。

首先说感情线,很多人都觉得男女主的情感发展很奇怪。电影开头铃芽觉得见过草太这里呼应了结尾在长世小铃芽遇见了男女主,后续草太变成椅子就奇怪起来了,从一只椅子去了解一个人太抽象了,而且一路上两人在做的都是关门师的任务,更像干活的同事。

不过对铃芽人物的刻画就好多了,叛逆、活泼又勇敢的女高中生。不过也许有的人更喜欢神明化身的猫,大臣的形象真的讨喜。铃芽把要石拔出来,大臣作为要石镇压灾厄的使命也完成了,并且使命转移到了关门师草太身上。大臣喜欢铃芽,而铃芽因为自己错误拔出要石而导致草太要作为要石牺牲,决心要救草太,这样就形成影片的故事冲突点。最终大臣被铃芽感动愿意重新化为要石镇压灾厄,也许是明白铃芽并不是真正意义上的喜欢自己,也许是明白身为神明的职责,这种二选一的剧情,看得还是让人难受的。

说回我认为的真正主题,影片的立意为了纪念地震,众所周知,日本是一个地震灾害多发的国家,自古以来饱受地震的影响。人们时时祈求神明的保佑,影片中提到“思念的重量,能够镇压住这片土地,有些地方已经被人们遗忘了,促使后门被打开。”神明人们的信仰而存在,当被人遗忘了就没法镇压土地,所以要思而复思,时刻感谢日不见之神的恩赐。同时,也要求人们要勇敢面对灾难,永远不放弃对生的渴望。铃芽最后把凳子给小时候的自己作为与过去告别,放下过去的伤痛,回到生活中,去体验爱情(与草太),珍惜活着的亲人朋友(姑姑岩户环),互相帮助有需要的人们(民宿店的千果,酒馆的老板瑠美)。

随手的写的感想,看起来很乱,思路也是乱的,网络上已经有很多人写了很多思路清晰的影评,比如可以看看这两篇影评:

广州的公园

作者 zguishen
2022年12月31日 00:00

说起广州的旅游资源,大多人都不以为然,觉得广州没啥好玩的,更多的是讨论来广州吃什么东西。其实除了一些知名的旅游景点之外,广州有很多公园,也是平常出去活动活动的好去处,适合常住在广州偶尔周末或放假去转转,不过作为旅行路线的就不推荐了。

广州有条徒步路线——广州云道,从中山纪念堂到白云山山脚,连接了很多个公园。

广州云道

这一片地区刚好分布了许多公园,甚至可以把流花湖公园一起连接起来,徒步的时候走个够。

广州公园

不过我自己去过的公园也不算多,以后有时间再去其他公园转转。下面写一些去过的公园的印象。

天河公园

天河公园是去过最多次的公园了,以前在信息港上班的时候就经常午饭过去逛逛。而且我住在附近,夏天有时回去跑步。

天河公园

流花湖公园

流花湖在越秀公园西南方向,里面有个流花东苑艺博物馆,展出的东西全是各种各样的石头,挺好玩的;还有个法兰克福玫瑰园,当时冬天去的不是时候,春夏应该比较好看;另外还有一座白宫风格的酒楼,不过不开放好像也没维护的,看起来比较破败了。

白宫

公园里有很多猫。

流花湖公园的猫咪

越秀公园

越秀公园很大,里面的景点也很多,比如中山纪念堂、五羊雕像、镇海楼(广州博物馆)。

中山纪念堂其实没什么好看的,门票还要10元,里面就是个剧院而已。

中山纪念堂

熟悉的五羊雕像。

中山纪念堂

镇海楼也要10元门票,上次去没进去。

岳麓公园

岳麓公园冬天去拍松林挺好的,可惜我拍了好多都不好看......

岳麓公园

云台花园

门票10元,园中又遍植中外四季名贵花卉。花园旁边还有个白云山索道,可以直接上白云山,上山单程票25元。

云台花园

❌
❌