Monday, November 22, 2021

V2EX - 技术

V2EX - 技术


从程序员到经营温泉度假酒店

Posted: 22 Nov 2021 05:13 AM PST

10 年沉淀,终于迈出了这一步。

自从有了娃,周末带老婆孩子去打卡各种景区便成了家常便饭。然而一天玩下来可以说是非常疲惫。偶然间跟朋友去了几家休闲会所 /温泉度假酒店,便爱上了这种场所。约 169 的门票,可以吃两顿自助餐,影院、水吧、书房、网吧,性价比极高且身心放松。

创建美区 google account?

Posted: 22 Nov 2021 05:07 AM PST

RT

SO 只用了 9 台 Web 服务器

Posted: 22 Nov 2021 05:05 AM PST

stackexchange.com/performance

StackOverflow (全球排名 188 )每月几亿的 PV (每月 55TB 网络流量),只需要 9 台 Web 服务器( RAM 64GB ),4 台数据库服务器。不过开销最大的还是用了 3 台 ES ( RAM 196G )。

技术栈是 C#(.Net),还是挺强的。

notion 和 wolai 选哪个?

Posted: 22 Nov 2021 05:00 AM PST

说实话 wolai 刚出的时候我是压根没怎么关注 wolai 的 毕竟是仿品,对我来说没有关注的必要。
但是最近又看到了这个 app ,试用了下发现还挺不错,主要是中文友好,
所以有点想换成 wolai 了, 但是有几个问题:

  1. 免费版限制有些多
  2. 数据表的功能以及其他的一些功能太少,虽然说未来可能都会开发完善

所以大家怎么选呢,notion 还是 wolai 有点纠结

如何获得叮咚买菜的 api

Posted: 22 Nov 2021 04:34 AM PST

家里人买菜要经常刷刷刷价格和剩余库存,想帮他做一个小工具自动提醒,拦截了一下请求发现都是 https 无法查看具体内容,求教如何获得叮咚买菜的 api

为女儿打造的一款 App,汉字笔顺

Posted: 22 Nov 2021 04:34 AM PST

女儿明年就要幼儿园毕业,作为宝爸的我,既兴奋又焦虑,一方面想让她快快乐乐成长,另一方面又不想让她输在起跑线,玩乐、学习之间的平衡是很难掌握的。

于是我开始从女儿的生活习惯、作息及爱好等方面,研究如何让女儿能够找到自己的节奏,自己去合理安排自己的时间,于是乎,我便着手开发一些 App ,以让她能够通过一些工具,合理掌握自己的学习时间。

因为女儿马上要小学,学习汉字成为了一个必经的过程,老婆和我变着手研究了各个版本教材,每个阶段要学习的汉字,按照初级、中级、高级、小学一年级上、小学一年级下等等,来分类这些汉字,并且根据她的年龄,设置每天要学习的汉字数量,就这样,一步一步的实现了学汉字 app 的第一个版本,后期又进行了几个版本的迭代,女儿每天只需要打开 app ,学完当天要学的汉字,因为操作简单,她自己就可以完成这些动作,学完之后,立即关掉平板,到公园骑自行车、玩耍、跳绳等等。

因为想要她自己安排自己的学习,因此我老婆将 App 设计的极其简单,又分类清晰,让女儿可以在没有家长陪同的情况下,自己学习,自己进步。经过一个多月的试验,女儿表现的还是很棒的,基本都按时完成了学习任务,对此,老父亲表示很欣慰。

同时我将这款 App"汉字笔顺"发布到了 AppStore ,暂时只有苹果版本,希望可以帮到那些跟我们一样,对于孩子学习有焦虑的宝爸,宝妈,让孩子拥有一个快乐的童年,我们需要一起努力。

AppStore 搜索"汉字笔顺",蓝色的 icon ,老铁们可以下载看下,提提意见,我会持续优化。

** https://itunes.apple.com/cn/app/id1576661299?mt=8 **

请教 Java 类继承问题

Posted: 22 Nov 2021 04:31 AM PST

@Service
public class AuthUserService {
@Autowired
private AuthUserDao authUserDao;

// ----> start
public static AuthUserService self;

@Autowired
public void setService(AuthUserService authUserService) {
AuthUserService.self = authUserService;
}
// ---> end 这部分我想用 BaseService 之类的复用继承
}

public interface AuthUserDao extends JpaRepository<AuthUser, Long> {

}

我的目的是能通过 AuthUserService .self.functionName() 之类的直接通过类名调用 service 的方法。
各位 V 友,请教该怎么写 BaseService ,或者有没有更好的方案?

2021 了, typescript 自动格式化的最佳实践是啥

Posted: 22 Nov 2021 04:25 AM PST

请教芯片原厂如何接入 Google Widevine / Netflix / Disney + 的认证

Posted: 22 Nov 2021 04:06 AM PST

RT.

背景介绍如下:

  1. 公司芯片是 ARM 架构, 支持主流 codec 4K 解码, HDR, HDCP 等标准.
  2. 已有 ARM TEE 实现.
  3. 已经在联系 Google Widevine team 获取 SDK.

现在想加入对 Netflix Disney+ 的认证处理. 但是作为程序员以前没有碰过商务的东西. 想问问这里有没有老哥搞过相关的工作. 解释下或者提供下网站也行. netflix.com 上没看到有用的信息.

芯片认证 和 设备认证 看起来是两部分. 但是首先要有芯片认证, 后续才有对应 OEM/ODM 产品的设备认证.

idea 有没有这样的 git commit 模板设置或支持的插件

Posted: 22 Nov 2021 04:02 AM PST

就是每次进来 commit message 交互窗口,然后固定显示预先设置的模板,如每次进来

fix: #
feat: #

最终我填写的会大概会是这样子的

fix: #任务 ID456 修复 BUG
feat: #任务 ID456 修复 BUG

先说明两个事:

网上都在推荐的一个插件 git commit template ,我也安装了,发现还是不能符合满足我的需求,

1.每次提交只能有一种类型的,要么 fix 或者只能 feat ,先预设含有两种类型的模板的好处是,如果只需要一种类型的提交,那么我会手动删除其中一条类型,如果需要增加的话,我会手动增加额外的类型(频率很低)

2.我知道一次提交包含 fix 和 feat 两种类型可能不符合标准规范,但是这种情况很少,暂且不讨论,而预设模板有两种类型就是因为删除比增加更快更高效率

请有经验的 V 友指导一下

关于程序员书屋的一个想法

Posted: 22 Nov 2021 03:48 AM PST

痛点:

  1. 很多国外的文档需要翻墙才能查看
  2. 就算能查看,延迟高、速度慢
  3. 一些热门的技术国内有中文版,但是比较分散

综上,想做一个网站,支持 Web 、App ,集合国内外的一些文档,按照技术类型进行分类,支持书签、收藏等功能,方便国内的程序员查看文档。

不知道各位有什么看法?

来请教下大佬们,关于家庭网络改个线!

Posted: 22 Nov 2021 03:39 AM PST

现在是这么个情况,电信一根光纤入户弱电箱,然后弱电箱往每个房间有一根网线,目前情况就是,所有设备都在弱电箱里面,弱电箱又是特别小的那种,设备已经都吊在外面了,现在我想把这些网络换到另一个小房间里去,光纤移过去,然后交换机再分到各个房间去,但是小房间跟弱电箱之间只有一根预埋的线管,里面穿了一根网线,在网上搜索了一圈,光纤可以用延长线通过预埋的线管拉到小房间,但是弱电箱里其他的几根网线,好像就没办法再走这根线管过去了(过 1 2 跟过的去,3 4 根实在过不去,线管不大)

在某宝上找了一圈,有那种比较细的网线(据说性能会下降,不太清楚),但感觉好像也没办法把好几根网线+1 跟光纤一起弄过去

有木有大佬对这块比较懂的,或者有没有相关设备推荐,能完成我这个需求,万分感谢

旁路由开启后,无法通过公网 ip 的远程桌面了是咋回事?

Posted: 22 Nov 2021 02:57 AM PST

主路由 k2p , 旁路由 docker openwrt, 未启用旁路由前,公网 ip 使用一切正常,启用旁路由后,公网 ip 无法远程桌面了,尝试在旁路由中使用端口二次转发,转发到需要远程桌面的机器上,但配置了还是无效,有人遇到过这种问题吗,如何解决呢?

有使用 harness 的吗?

Posted: 22 Nov 2021 02:26 AM PST

namespace 命名各位遵从什么原则

Posted: 22 Nov 2021 01:44 AM PST

各位请教下你们命名 namespace 的原则是什么? 是基于 test/stage/production ,还是基于项目然后 test/stage/production 。

亦或者其它更好的思路?欢迎指点。

有没有比较好用的 npm package 或者 js/ts sample 访问 git repo 的?

Posted: 22 Nov 2021 01:33 AM PST

现在需要写一个 javascript/typescript 脚本,操作 git repo ,
比如,

  • checkout a git repo
  • create a new branch
  • change some files
  • create PR
  • merge PR (push to remote)
  • ,etc.

有没有比较成熟好用的 package 可以直接使用,或者 javascript/typescript 的 sample 可以学习的?

谢谢先!

收集和使用表情包/梗图,有啥比较好的方案吗?

Posted: 22 Nov 2021 12:46 AM PST

经常在朋友圈、微博有人发了很有意思的表情包或梗图,想收集下来以后自己发朋友圈、发微博、聊天时可以用,有啥好方案或软件么?

注 1:不是只在微信聊天中使用,所以收藏成微信表情包并不实用。

注 2:现在能想到的就是在输入法里收藏,打字的时候可以调出。但有这种输入法么?

注 3:能打 tag 或有搜索功能的就最完美了。

xpack 监控日志 index 写入出错,导致正常写操作耗时高

Posted: 22 Nov 2021 12:05 AM PST

集群 3 个节点,都是 2c4g ,机器是 aws 的。es node 节点没有掉线,index 状态都是 green 。
线上监控到写 es 操作耗时高,查看日志报错'ShardNotFoundException',显示往 monitoring-es-7-xxxx 的 index 写数据报错。
利用 /_cat/shards 命令查看发现报错 index replica ,docs 、store 都不显示,手动 reroute 把这个 replica 分配到其他 node 报错消失。但是过几天这个监控日志 index 只要分配到之前报错的 node ,就有可能出现日志写不进的情况,但也不是 100%。目前还没有遇到过业务 index 写入失败的情况,可能是数据量比较小。
有大佬遇到过这种类似问题吗?可能会是哪些原因?

国内有做 CV(持续验证)的厂家吗?

Posted: 21 Nov 2021 11:55 PM PST

.net6 使用 UseHttpsRedirection 中间件强制将请求从 http 转 https,但 Authorization 信息丢失

Posted: 21 Nov 2021 11:54 PM PST

前端(前端是一个.netcore 2.1 的 mvc 项目)有一个带 Authorization 的 http 请求去 call api(api 是.net6 的),api 会使用 UseHttpsRedirection 中间件会将 http 转 https ,但转的过程中 Authorization 信息丢失了,导致返回 401 。两个项目都是在本地开发环境运行的。

各位大佬们,知道什么原因吗?

elasticsearch 嵌套文档查询问题

Posted: 21 Nov 2021 11:06 PM PST

求问一个 es 嵌套文档查询,仅当查询范围内所有嵌套文档都满足条件时才返回,而不是只要有一个嵌套文档满足条件就返回。

我的场景是按天建立嵌套文档,筛选时给定日期范围,需要筛选出该日期范围内每天都满足筛选条件的文档,目前我自己写的搜索语法只要日期范围内有一天满足就会返回..

我的文档结构如下:

{     "mappings": {         "properties": {             "adResource": {                 "type": "nested",                 "properties": {                     "day": {                         "type": "date",                         "format": "yyyy-MM-dd || epoch_millis"                     },                     "total": {                         "type": "integer"                     },                     "subResource": {                         "type": "nested",                         "properties": {                             "hours": {                                 "type": "integer"                             },                             "total": {                                 "type": "integer"                             }                         }                     }                 }             }         }     } } 

数据示例:

{     "adResource": [         {             "total": 1800,             "subResource": [                 {                     "hours": 0,                     "total": 0                 },                 {                     "hours": 3600,                     "total": 0                 },                 {                     "hours": 7200,                     "total": 0                 },                 {                     "hours": ...,                     "total": ...                 },                 {                     "hours": 82800,                     "total": 0                 }             "day": "2021-11-20"         },         {            "total": 5200,            "subResource":[......]            "day": "2021-11-21"         }     ] } 

需求:检索的时候给定一个日期区间和小时区间,要求检索出日期区间和小时区间内资源全部满足的文档,例如给定检索条件:

{     "dayStart": "2021-11-20",     "dayEnd": "2021-11-21",     "total": 540,     "subResource": [         {             "hour": 0,             "total": 180         },         {             "hour": 3600, // hour 3600 表示 1:00:00 到 1:59:59 的区间             "total": 360         }     ] } 

表示 20 、21 这两天的 total>540 ,且这两天内的 hour 0 > 180 && hour 3600 > 360

目前的搜索语法如下,该搜索会返回 20 、21 这两天只有一天满足条件的文档,而我需要的是两天都满足的文档:

{   "query": {     "bool": {       "filter": [         {           "nested": {             "path": "adResource",             "query": {               "bool": {                 "must": [                   {                     "range": {                       "adResource.day": {                         "gte": "2021-11-20",                         "lte": "2021-11-21"                       }                     }                   },                   {                     "nested": {                       "path": "adResource.subResource",                       "query": {                         "bool": {                           "must": [                             {                               "bool": {                                 "must": [                                   {                                     "term": {                                       "adResource.subResource.hours": {                                         "value": "0"                                       }                                     }                                   },                                   {                                     "range": {                                       "adResource.subResource.total": {                                         "gte": 180                                       }                                     }                                   }                                 ]                               }                             },                             {                               "bool": {                                 "must": [                                   {                                     "term": {                                       "adResource.subResource.hours": {                                         "value": "3600"                                       }                                     }                                   },                                   {                                     "range": {                                       "adResource.subResource.total": {                                         "gte": 360                                       }                                     }                                   }                                 ]                               }                             }                           ]                         }                       }                     }                   }                 ]               }             }           }         }       ]     }   } } 

如何对子域名下的网页注入脚本

Posted: 21 Nov 2021 10:43 PM PST

我搞了一个登录页面,用来给一些没有鉴权的第三方的网页做鉴权网关,用 nginx 的 auth_request 做的。 实现的效果是,第三方网页接入 nginx 后,访问反向代理地址如果未登录就跳转到登录页面(有点像是 basicAuth )。

但是登录成功,重定向会第三方网页后,没有登出按钮,只能手动地址栏输入 ..../logout 来登出。

我想知道对子域下的网页理论上是否能实现注入一个登出按钮?有什么限制?

主要是不想改第三方的代码,也改不动。

(我大概了解了 XSS 注入,但感觉这个得利用网页表单提交等漏洞,而且没有通用性。)

关于 socket 的一些问题

Posted: 21 Nov 2021 10:28 PM PST

最近在学习计算机网络,看到 socket 时产生了一个问题。socket 是 tcp/udp 等的一个抽象接口,那么是不是几乎所有使用 tcp/udp 来建立连接的都需要通过 socket 来调用 tcp/udp ?比如 http 协议等。 网络传输的报文是在传输层来寻找对应端口号,这个是不是就是通过 socket 来完成的? socket 在客户端与服务端建立连接时会生成一个新的 socket ,在客户端或者服务端调用 close 方法时断开这个连接,那么 http 协议每次请求是不是都会创建一个 socket?在服务端响应完毕后关闭这个 socket 连接?

关于提升编程效率的两点想法

Posted: 21 Nov 2021 10:04 PM PST

第一是大项目的代码模块,一开始就要按照功能划分为很多子模块。不能大海捞针一样的,直接在几万行代码库里修改。

否则过大的程序代码库,就意味着大脑负担大。同时数据量大,加载慢,调式慢,运行慢,多耦合,写代码自然也慢了。

而轻量级的数据,能让单元测试写起来很顺手,事半功倍。

第二是语法不能定太死,尽可能根据项目需求,引入 DSL ( domain-specific language ),来最大限度降低代码的阅读难度。

条件允许的情况下,可以对源代码做二次预处理(比如 QT )。

举个例子,JSX 语法肯定要比传统的 JS 拼接字符串有更高的可读性,但是代码都是必须经过编译的。

移动端与 pc 端 cookie 策略是否不同

Posted: 21 Nov 2021 07:42 PM PST

我们的系统设置的过期时间设置了 5 天。pc 端登陆后,5 天之内登录状态保持正常。 但是移动端 过一会儿刷新,就退出登陆了,怀疑是不是因为移动端和 pc 的 cookie 策略是否不同导致的呀? 有没有大手子知道的,给点思路

OushuDB 产品介绍

Posted: 21 Nov 2021 05:41 PM PST

一、查询执行流程

用户通过 JDBC/ODBC 提交查询之后,查询解析器解析查询得到查询树,然后优化器根据查询树生成查询计划,派遣器和资源管理器交互得到资源,分解查询计划,然后派遣计划到 Segment 的执行器上面执行。最终结果会传回给用户。

二、弹性调度执行
弹性执行引擎有几个关键设计点:存储和计算的完全分离,无状态 Segment 以及如何使用资源。存储和计算的分离使得我们可以动态的启动任意多个虚拟 Segment 来执行查询。无状态 Segment 使得集群更容易扩展。要想保证大规模集群的状态一致性是比较困难的问题,所以我们采用了无状态的 Segment 。如何使用资源包括如何根据查询的代价申请多少资源,如何有效的使用这些资源以及如何使得数据局部性最优。OushuDB 内部针对每一个部分都进行了优化的设计。

三、极速执行器
执行器是数据库最核心的部件之一,Oushu Database 对执行器进行了完全重新设计,充分利用了最新 CPU 的每一个特性,比如 SIMD 指令等,可以做到性能的极致。

OushuDB 体系架构概览

Posted: 21 Nov 2021 04:11 PM PST

图 1 给出了一个典型的 OushuDB 集群的主要组件。计算部分和存储部分完全分离,可以独立扩容。在图中有多个 OushuDB Master 节点。元数据管理服务和资源管理服务位于 OushuDB Master 内部。其他节点为 Slave 节点。每个 Slave 节点上安装有一个 OushuDB Segment 。Segment 实现 OushuDB 的计算。OushuDB Segment 在执行查询的时候会启动多个 QE (Query Executor, 查询执行器)。查询执行器运行在资源容器里面。在这个架构下,节点可以动态的加入集群,并且不需要数据重新分布。当一个节点加入集群时,他会向 OushuDB Master 节点发送心跳,然后就可以接收未来查询了。

在 OushuDB master 节点内部有如下几个重要组件:查询解析器( Parser/Analyzer ),优化器,资源管理器,容错服务,查询派遣器,元数据服务。在查询执行时,针对一个查询,弹性执行引擎会启动多个虚拟 Segment 同时执行查询,节点间数据交换通过 Interconnect (高速互联网络)进行。如果一个查询启动了 1000 个虚拟 Segment ,意思是这个查询被均匀的分成了 1000 份任务,这些任务会并行执行。所以说虚拟 Segment 数其实表明了查询的并行度。查询的并行度是由弹性执行引擎根据查询大小以及当前资源使用情况动态确定的。下面我逐个来解释这些组件的作用以及它们之间的关系:
● 查询解析器:负责解析查询,并检查语法及语义。最终生成查询树传递给优化器。
● 优化器:负责接受查询树,生成查询计划。针对一个查询,可能有数亿个可能的等价的查询计划,但执行性能差别很大。优化器的作用是找出优化的查询计划。
● 资源管理器:资源管理器负责整个集群的资源管理。资源管理器需要在并发的查询之间分配资源,并保证查询不使用超过分配给该查询的资源,否则查询之间会相互影响,可能导致系统整体不可用。
● 元数据缓存:用于 OushuDB 确定哪些 Segment 扫描表的哪些部分。OushuDB 需要把计算派遣到数据所在的地方,所以我们需要匹配计算和数据的局部性。这些需要底层存储数据(比如 HDFS 块,Magma Range 等)的位置信息。位置信息一般在底层存储中,每个查询都访问底层存储中的元数据,会形成瓶颈。所以我们在 OushuDB Master 节点上建立了元数据缓存。
● 容错服务:负责检测哪些节点可用,哪些节点不可用。不可用的机器会被排除出资源池。
● 查询派遣器:优化器优化完查询以后,查询派遣器派遣计划到各个节点上执行,并协调查询执行的整个过程。查询派遣器是整个并行系统的粘合剂。
● 元数据服务:负责存储 OushuDB 的各种元数据,包括数据库和表信息,以及访问权限信息等。另外,元数据服务也是实现分布式事务的关键。
● 高速互联网络:负责在节点之间传输数据。使用软件实现,基于 UDP 协议。UDP 协议无需建立连接,从而可以避免 TCP 高并发连接数的限制。

找到 git 中特定文件被忽略的原因

Posted: 21 Nov 2021 01:55 PM PST

git check-ignore -v <path>格式是<source> <COLON> <linenum> <COLON> <pattern> <HT> <pathname>

例如,

[DBG]: PS> git check-ignore -v  (GetFileName 0)   .gitignore:1:file_0.txt file_0.txt 

帮忙分析下这段 nodejs 代码的内存泄漏原因

Posted: 21 Nov 2021 07:50 AM PST

有一个实时应用,使用 nodejs 编写,会每隔一段时间调用远程 grpc ,大概每秒 1 、2 次这样的调用。上线一个月后发现服务器内存占用越来越大。大概占用了 14GB 的内存吧。用 iotop 发现 node 内存炸了。

使用了 alinode dump heap 了,发现了有一个 promisifyCall 占用了大量内存,疑似泄露。

调用链是

自身大小(字节) 总大小(字节)      函数 0              524600         processTicksAndRejections   internal/process/task_queues.js 0              524600           updateStatus              我自己的文件 0              524600              publish                这里调用了 grpc 导出的函数 524600         524600                promisifyCall        这里应该就是泄露的函数了 

promisifyCall 来自于 https://github.com/bojand/promisify-call ,看了下是被 grpcCaller 引用的 https://github.com/bojand/grpc-caller 。项目中使用了 grpcCaller 去调用 grpc 方法。

const res = await grpcCallerInstance.publish(req); 

接着这个 publish 操作就走到promisifyCall中去了

promisifyCall 的定义看了下,https://github.com/bojand/promisify-call/blob/master/index.js

const wc = require('with-callback')  /**  * Promisifies the call to <code>fn</code> if appropriate given the arguments.  * Calls the function <code>fn</code> either using callback style if last argument is a function.  * If last argument is not a function, <code>fn</code> is called returning a promise.  * This lets you create API that can be called in either fashions.  * @param  {Object}   ctx  context / this  * @param  {Function} fn   The function to call  * @param  {arguments}   args Arguments  * @return {undefined|*|Promise}  Promise if promisified  */ function promisifyCall (ctx, fn) {   const args = []   args.push.apply(args, arguments)   args.splice(0, 2)   // check if last (callback) argument is being pased in explicitly   // as it might be undefined or null, in which case we'll replace it   const same = fn.length && args.length === fn.length   const lastIndex = same ? fn.length - 1 : args.length - 1   const lastArg = args && args.length > 0 ? args[lastIndex] : null   const cb = typeof lastArg === 'function' ? lastArg : null    if (cb) {     return fn.apply(ctx, args)   }    return wc(callback => {     same       ? args[lastIndex] = callback       : args.push(callback)     fn.apply(ctx, args)   }) } 

隐约感觉里面的 args 变量可能会导致泄露。但还是没想明白怎样才会发生这个泄露。

被客户告知 HTTP 的 PUT 请求不安全,甩锅给我们要求整改

Posted: 21 Nov 2021 05:39 AM PST

真的是要吐,这段时间好几个客户单位反馈业务系统无法使用,一直报错

上去一看,PUT 请求全部被拦截,也不知道家防火墙商干的好事,默认把 PUT 请求全部给禁了,还给客户培训说 PUT 请求就是不安全

给客户解释了半天,根本不听,就说防火墙这样设置肯定有他的道理,说是我们使用了不安全的请求方法,要我们负责安全整改

全站遵循 RESTful 设计的呀~简直要疯了~也不知道哪个 wbd 做的默认规则

一个 MySQL 查询问题

Posted: 21 Nov 2021 03:33 AM PST

有一张私信表表结构如下图

1637548753322.png

我想把当前用户的所有私信查询出来,我现在使用的下面的查询语句

# 假设当前用户 id 为 12 select * from t_message where receiver_id == 12 group by sender_id; 

但是这样有两个问题

  1. 如果是 12 用户给其他用户发送的私信,如果对方没有回复的话,那么就查询不出来。
  2. MySQL5.7 以后默认开启了ONLY_FULL_GROUP_BY,用上面的查询语句会报错,要关掉才可以。

请求各位大佬,怎么解决小弟这个问题?

No comments:

Post a Comment