文章目录
- 前言
- Redis问题
- 启用碎片自动回收失败
- 启动Redis未脱离终端
- Vercel问题
- 未在Vecel团队的人提交无法触发自动部署
- 更新package.json后部署Vercel时报错
- Android问题
- 主动请求通知权限
- 网络状态变化的监听不能使用静态注册
- 各种Service介绍和对比
- 总结
前言
这两天的工作又相对杂乱一些,处理一下A事情,又要搞一搞B事情,需要盯着C事情,还要尝试一下D事情,所以这篇总结没有什么主线,主要目的是记录一下最近解决的问题,先不展开讨论,当类似的问题积累一些再展开描述,我就先记录一下流水账了。
Redis问题
本来是一个常规清理数据,Redis回收内存碎片的操作,但是因redis-server版本问题被迫切换解决方案
启用碎片自动回收失败
127.0.0.1:6379> config set activedefrag yes
(error) ERR Active defragmentation cannot be enabled: it requires a Redis server compiled with
a modified Jemalloc like the one shipped by default with the Redis source distribution
开启主动碎片整理机制失败,提示错误大概意思是说,当前的 Redis 服务器未使用支持**主动碎片整理(Active Defragmentation)**的 Jemalloc 分配器,因为Redis 的主动碎片整理功能依赖于 Jemalloc 分配器,如果 Redis 是使用其他分配器(如 libc malloc)编译的,或者当前的 Jemalloc 缺乏必要的功能,就会导致该功能无法启用。
可以通过Redis命令 redis-cli INFO memory | grep allocator
来查询,如果显示 mem_allocator:jemalloc
,则表示启用了 Jemalloc。如果显示 mem_allocator:libc
,则说明当前未使用 Jemalloc,需要重新安装或编译 Redis。
我运行一看当然是 mem_allocator:libc
了,看来没办法进行碎片整理了,幸好是个slave节点,所以我干脆保存数据后重启一下吧。
启动Redis未脱离终端
首先关闭redis服务
shutdown SAVE
这个过程会进行同步存储,所以会给你一种卡死了的状态,因为我这里有90G数据,存储大概用了10多分钟,Redis服务成功关闭
然后按配置文件启动Redis
/usr/local/bin/redis-server ./redis.conf
启动完终端就停在这个了,难道我起了个前台程序?我记得之前都是这么启动的啊,总不能为了脱离终端我还要使用 nohup
和 &
来配合吧,有点low啊,主要是日志是不是就放到nohup.out文件里了,找找有没有配置吧,打开redis.conf文件发现daemonize no
,我的天啊,这个redis-server为什么这么与众不同,守护进程模式居然是关着的,虽然说把配置文件改成 daemonize yes
再启动就好了,但是我好奇的是之前是怎么启动的。
我决定不修改配置文件了,保持原来的样子,直接在启动时指定 --daemonize yes
好了,这样可以达到目的,在Linux系统中使用Redis时,命令行参数和配置文件参数(也称为Redis配置)具有不同的优先级。如果同一个配置选项在配置文件和命令行参数中被设置,那么命令行参数将覆盖配置文件中的设置。
Vercel问题
之前简单聊过将Nextjs框架编写的网站部署到Vecel有天然的适应性,因为Vercel背后的团队就是开发出Nextjs框架的那群人,部署到Vercel以后绑定一个域名就可以使用了,不需要自己安装运行环境,不需要配置DN缓存,不需要配置SSL证书,真是方便极了,但免费的账户是有资源限制的,特别是要团队开发的话需要购买付费版本。
未在Vecel团队的人提交无法触发自动部署
用过Vercel就会发现,当通过github导入项目之后,以后每次推送到github后都能自动触发Vercel的网站发布功能,但是未添加到Vercel团队的账号提交时无法触发自动部署,提示
Vercel - No GitHub account was found matching the commit author email address
一种办法就是把所有可能提交的账号都添加到Vercel团队,但是这是要花钱的,每添加一个成员每月$20,还有就是找已经在团队的成员再提交一次,触发自动部署就行了,这个成本也很低,也就是仓库记录不那么干净了而已
git commit --allow-empty -m"trigger redeployment"
更新package.json后部署Vercel时报错
因为我使用 npm install
初始化完项目后,启动时有两个警告
Module not found: Can't resolve 'bufferutil' in 'E:\WorkSpace\nodeweb\qxweb\node_modules\ws\lib'
Import trace for requested module:
./node_modules/ws/lib/buffer-util.js
./node_modules/ws/lib/websocket.js
./node_modules/ws/index.js
./node_modules/@supabase/realtime-js/dist/main/RealtimeClient.js
./node_modules/@supabase/realtime-js/dist/main/index.js
./node_modules/@supabase/supabase-js/dist/main/index.js
./store/SupabaseStore.tsx
./node_modules/ws/lib/validation.js
Module not found: Can't resolve 'utf-8-validate' in 'E:\WorkSpace\nodeweb\qxweb\node_modules\ws\lib'
Import trace for requested module:
./node_modules/ws/lib/validation.js
./node_modules/ws/lib/websocket.js
./node_modules/ws/index.js
./node_modules/@supabase/realtime-js/dist/main/RealtimeClient.js
./node_modules/@supabase/realtime-js/dist/main/index.js
./node_modules/@supabase/supabase-js/dist/main/index.js
./store/SupabaseStore.tsx
所以我就手动安装了这两个库 npm isntall bufferutil utf-8-validate
,这就导致我的 package.json
文件更新内容里增加了这两个库的引用版本,提交代码部署Vercel时报错
Running build in Washington, D.C., USA (East) – iad1
Cloning github.com/2338-AI/quantum-solutions-web (Branch: main, Commit: 96249d1)
Previous build cache not available
Cloning completed: 2.607s
Running "vercel build"
Vercel CLI 39.1.1
Detected `pnpm-lock.yaml` version 9 generated by pnpm@9.x
Installing dependencies...
ERR_PNPM_OUTDATED_LOCKFILE Cannot install with "frozen-lockfile" because pnpm-lock.yaml is not up to date with <ROOT>/package.json
Note that in CI environments this setting is true by default. If you still need to run install in such cases, use "pnpm install --no-frozen-lockfile"
Failure reason:
specifiers in the lockfile ({"@douyinfe/semi-ui":"^2.44.0","@react-spring/web":"^9.7.5","@supabase/supabase-js":"^2.38.1","accept-language":"^3.0.18","classes-names":"^1.0.0","emailjs-com":"^3.2.0","i18next":"^23.5.1","i18next-browser-languagedetector":"^7.1.0","i18next-resources-to-backend":"^1.1.4","is-mobile":"^5.0.0","next":"13.5.4","react":"^18","react-dom":"^18","react-google-recaptcha":"^3.1.0","react-i18next":"^13.3.0","swiper":"^11.1.14","tailwind-merge":"^2.5.4","@types/node":"^20","@types/react":"^18","@types/react-dom":"^18","@types/react-google-recaptcha":"^2.1.7","@typescript-eslint/eslint-plugin":"^6.3.0","@typescript-eslint/parser":"^6.3.0","autoprefixer":"^10","eslint":"^8","eslint-config-airbnb":"^19.0.4","eslint-config-airbnb-typescript":"^17.1.0","eslint-config-next":"13.5.4","postcss":"^8","postcss-pxtorem":"^6.1.0","prettier":"^3.3.3","prettier-plugin-tailwindcss":"^0.6.8","tailwindcss":"^3","typescript":"^5"}) don't match specs in package.json ({"@types/node":"^20","@types/react":"^18","@types/react-dom":"^18","@types/react-google-recaptcha":"^2.1.7","@typescript-eslint/eslint-plugin":"^6.3.0","@typescript-eslint/parser":"^6.3.0","autoprefixer":"^10","eslint":"^8","eslint-config-airbnb":"^19.0.4","eslint-config-airbnb-typescript":"^17.1.0","eslint-config-next":"13.5.4","postcss":"^8","postcss-pxtorem":"^6.1.0","prettier":"^3.3.3","prettier-plugin-tailwindcss":"^0.6.8","tailwindcss":"^3","typescript":"^5","@douyinfe/semi-ui":"^2.44.0","@react-spring/web":"^9.7.5","@supabase/supabase-js":"^2.38.1","accept-language":"^3.0.18","bufferutil":"^4.0.8","classes-names":"^1.0.0","emailjs-com":"^3.2.0","i18next":"^23.5.1","i18next-browser-languagedetector":"^7.1.0","i18next-resources-to-backend":"^1.1.4","is-mobile":"^5.0.0","next":"13.5.4","react":"^18","react-dom":"^18","react-google-recaptcha":"^3.1.0","react-i18next":"^13.3.0","swiper":"^11.1.14","tailwind-merge":"^2.5.4","utf-8-validate":"^6.0.5"})
Error: Command "pnpm install" exited with 1
这个错误提示说明 Vercel 在部署过程中使用 pnpm
安装依赖时,遇到了 pnpm-lock.yaml
和 package.json
文件中的依赖不匹配问题。具体来说,pnpm-lock.yaml
文件中的依赖版本与 package.json
中声明的版本范围不一致,导致 pnpm
无法继续安装。
看到这时我才意识到我的项目里有个 pnpm-lock.yaml
,而我使用的 npm
安装,本地有个 package-lock.json
文件,这样一看是我工具用错了呀,我说这个项目怎么没提交 package-lock.json
文件呢
清理模块重新安装吧
rm -rf .node_modules package-lock.json
pnpm install
输出如下
PS E:\WorkSpace\nodeweb\qxweb> pnpm install
╭──────────────────────────────────────────────────────────────────╮
│ │
│ Update available! 9.5.0 → 9.14.2. │
│ Changelog: https://github.com/pnpm/pnpm/releases/tag/v9.14.2 │
│ Run "pnpm add -g pnpm" to update. │
│ │
│ Follow @pnpmjs for updates: https://x.com/pnpmjs │
│ │
╰──────────────────────────────────────────────────────────────────╯
Lockfile is up to date, resolution step is skipped
Packages: +423
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
WARN GET https://registry.npmmirror.com/next/-/next-13.5.4.tgz error (ECONNRESET). Will retry in 10 seconds. 2 retries left.
Downloading @next/swc-win32-x64-msvc@13.5.4: 36.94 MB/36.94 MB, done
Downloading next@13.5.4: 16.80 MB/16.80 MB, done
Downloading typescript@5.2.2: 7.23 MB/7.23 MB, done
Progress: resolved 423, reused 184, downloaded 239, added 423, done
node_modules/.pnpm/es5-ext@0.10.62/node_modules/es5-ext: Running postinstall script, done in 43.1s
node_modules/.pnpm/utf-8-validate@5.0.10/node_modules/utf-8-validate: Running install script, done in 52.3s
node_modules/.pnpm/bufferutil@4.0.8/node_modules/bufferutil: Running install script, done in 52.3s
dependencies:
+ @douyinfe/semi-ui 2.44.0
+ @react-spring/web 9.7.5
+ @supabase/supabase-js 2.38.1
+ accept-language 3.0.18
+ classes-names 1.0.0
+ emailjs-com 3.2.0
+ i18next 23.5.1
+ i18next-browser-languagedetector 7.1.0
+ i18next-resources-to-backend 1.1.4
+ is-mobile 5.0.0
+ next 13.5.4
+ react 18.2.0
+ react-dom 18.2.0
+ react-google-recaptcha 3.1.0
+ react-i18next 13.3.0
+ swiper 11.1.14
+ tailwind-merge 2.5.4
devDependencies:
+ @types/node 20.8.6
+ @types/react 18.2.28
+ @types/react-dom 18.2.13
+ @types/react-google-recaptcha 2.1.7
+ postcss-pxtorem 6.1.0
+ prettier 3.3.3
+ prettier-plugin-tailwindcss 0.6.8
+ tailwindcss 3.3.3
+ typescript 5.2.2
Done in 21m 2.3s
这次再启动项目 pnpm run dev
就不报缺失模块的警告了
简单说下 npm
和 pnpm
的关系,npm
是 Node.js 的默认包管理工具,用于安装、管理和共享 JavaScript 包。安装 Node.js 时会自带 npm,不需要额外安装。pnpm
是一种兼容 npm 和 yarn 的包管理工具。它与 npm 类似,也用来管理 JavaScript 包,但通过优化磁盘使用和依赖解析性能解决了 npm 的一些问题。
再简单说下 package.json
、package-lock.json
和 pnpm-lock.yaml
的关系
package.json
描述项目的基本信息(如项目名称、版本号),声明项目的依赖项、脚本命令和配置,是项目中最重要的依赖描述文件,是开发者直接编辑的文件,但不包含依赖的具体版本信息package-lock.json
是 npm 包管理工具生成的锁定文件,记录依赖的具体版本号和结构,确保不同环境中安装的依赖版本一致。不需要手动编辑,由 npm 自动生成和管理。pnpm-lock.yaml
是 pnpm 包管理工具生成的锁定文件,用于精确记录依赖的版本号和安装结构。确保团队或 CI/CD 环境在安装依赖时,所有人使用的依赖版本完全一致。不需要手动编辑,由 pnpm 自动生成和管理。
可以看到package-lock.json
和 pnpm-lock.yaml
是互斥的,平时 npm
和 pnpm
选择一个就行,别混着用。
Android问题
Apple推送选择APNs就好了,而Android推送一直就是老大难,因为FCM对谷歌框架的依赖,国内基本是不可用的,所以在之前的蛮荒年代,真是八仙过海各显其能,相互拉起、定时检测等等搞得手机卡顿的不行,后来各家厂商基本都实现了自己的通道,比如华为、小米、OPPO、Vivo等等,所以找个聚合的SDK也是能办到的,但是国外还是主要依赖FCM。
但随着Android版本的提升,推送的要求越来越严,一旦应用被强制杀死,FCM消息虽然能达到手机,但是不允许拉起应用,导致消息无法触及到用户,这也是目前国外环境要实现推送所面临的难点。
要想能收到FCM的推送消息,就得保证应用不被杀死,可能得方向有提高优先级、定时检测重启、杀死后立即重启、采用其他的推送通道等等,但是效果都不理想,之前好用的方式升级版本或者切换一个手机厂商就不太好使了,没有什么通用的解决办法。
后来发现,真对国内手机只要给应用开启自启动权限,那么即使被强杀后也能收到FCM通知,或许这是一个可以努力的方向,但这个权限不是通用的逻辑,每个手机厂商都有自己的白名单,大厂APP出生就在白名单里,而我们自己开发的APP需要经过复杂的引导操作才能加入其中,虽然很难,但终归是第一条可行的路。
主动请求通知权限
在 Android 13 (API 33) 及以上版本,应用需要 主动请求通知权限,以便能够发送通知。对于之前的版本,应用只需要在 AndroidManifest.xml
中声明 POST_NOTIFICATIONS
权限即可。然而,从 Android 13 开始,仅在声明权限的基础上,还需要在运行时申请此权限。
步骤1:修改 AndroidManifest.xml
首先,在 AndroidManifest.xml
文件中声明权限:
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
步骤2:在应用中请求通知权限
从 Android 13 开始,你需要在运行时请求通知权限。你可以使用 NotificationManagerCompat
来检查和请求权限。
import android.os.Build;
import android.widget.Toast;
import androidx.core.app.NotificationManagerCompat;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 检查 Android 13 及以上版本是否需要请求通知权限
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
NotificationManagerCompat notificationManagerCompat = NotificationManagerCompat.from(this);
// 检查通知权限是否已被授权
if (!notificationManagerCompat.areNotificationsEnabled()) {
// 如果没有授权,可以弹出提示或者引导用户
requestNotificationPermission();
} else {
// 如果已授权,可以继续执行相关逻辑
Toast.makeText(this, "通知权限已授权", Toast.LENGTH_SHORT).show();
}
}
}
private void requestNotificationPermission() {
// 你可以在这里引导用户到设置页面手动授权
Toast.makeText(this, "请在设置中授权通知权限", Toast.LENGTH_LONG).show();
}
}
步骤3:引导用户授权
如果用户没有授权通知权限,你通常需要提供一个方法引导他们到设置页面,在该页面中手动授权。因为从 Android 13 开始,权限只能通过系统设置进行手动授权,而不是通过应用内弹窗。
private void goToSettings() {
Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS)
.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName());
startActivity(intent);
}
网络状态变化的监听不能使用静态注册
从 Android 7.0 (API 24) 开始,网络状态变化的监听不能使用静态注册 (<receiver>
标签在 AndroidManifest.xml
中注册),必须通过动态注册来实现。这是出于优化电池和性能的考虑,Android 不再允许应用通过静态注册的方式来监听系统广播(如网络变化、屏幕开关等),尤其是对敏感的系统事件。
静态注册 (AndroidManifest.xml)
静态注册会在 AndroidManifest.xml
中声明广播接收器,并且会在整个应用运行期间自动接收系统广播。对于网络状态变化(如 CONNECTIVITY_ACTION
)的静态注册从 Android 7.0 开始被限制。
AndroidManifest.xml 中的静态注册示例(在 Android 7.0 之前是有效的):
<receiver android:name=".NetworkChangeReceiver">
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
</intent-filter>
</receiver>
这种方式在 Android 7.0 及以后会被限制,无法接收到网络状态变化的广播。
动态注册 (代码中注册)
动态注册意味着在应用运行时,通过 Context.registerReceiver()
来注册接收器,这样可以选择性地在需要时注册,并在不需要时取消注册。这样做的好处是,可以更灵活地控制接收器的生命周期,避免不必要的电池消耗。
动态注册的示例:
public class MainActivity extends AppCompatActivity {
private NetworkReceiver networkReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 初始化网络广播接收器
networkReceiver = new NetworkReceiver();
IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
// 动态注册接收器
registerReceiver(networkReceiver, filter);
}
@Override
protected void onDestroy() {
super.onDestroy();
// 注销接收器
unregisterReceiver(networkReceiver);
}
}
public class NetworkReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// 获取网络连接状态
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
if (activeNetwork != null && activeNetwork.isConnected()) {
Log.d("NetworkReceiver", "网络已连接");
} else {
Log.d("NetworkReceiver", "网络未连接");
}
}
}
在这个示例中,使用 registerReceiver()
动态注册了一个监听网络状态变化的广播接收器 NetworkReceiver
,并在 onCreate()
中进行注册,在 onDestroy()
中进行注销,以避免内存泄漏。
各种Service介绍和对比
在 Android 中,Service
是一种后台组件,用于在应用中执行长时间运行的任务。根据服务的用途和生命周期,Android 提供了不同类型的服务。以下是 Android 中常见的 Service
类型和 BroadcastReceiver
的介绍及对比:
普通 Service (Normal Service) 没有前台 UI 组件。通常用于执行后台任务,不与用户交互,可以在应用的任何地方启动,并在后台运行,直到它完成工作或被显式停止,生命周期为 onCreate()
→ onStartCommand()
→ onDestroy()
,适用执行短时间的后台任务,如数据同步、文件下载等场景。
前台服务 (Foreground Service) 在运行时必须显示一个持续的通知(通知栏),因此它会在用户和系统资源管理中被认为是一个“重要”的服务,不容易被系统杀死。生命周期为 onCreate()
→ onStartCommand()
→ onDestroy()
(服务必须调用 startForeground()
来显示通知),适用执行长期任务,如播放音乐、导航、实时更新等场景。
Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("My Foreground Service")
.setContentText("Service is running in the foreground")
.setSmallIcon(R.drawable.ic_notification)
.build();
startForeground(1, notification);
JobService 是一种特殊类型的服务,它用于执行计划任务(即 JobScheduler
),在特定条件下(如网络连接、充电等)启动。JobService
适合用于执行延迟任务或条件任务,如周期性数据同步、定时上传等,与 JobScheduler
配合使用,可以在设备的空闲时间或在设备满足某些条件时执行任务。生命周期为 onCreate()
→ onStartJob()
→ onStopJob()
,适用执行定期任务,延迟任务,或者需要满足某些条件(如网络连接、充电等)的任务场景
public class MyJobService extends JobService {
@Override
public boolean onStartJob(JobParameters params) {
// 执行任务
return true; // 返回 true 表示任务正在进行中
}
@Override
public boolean onStopJob(JobParameters params) {
// 停止任务
return true; // 返回 true 表示任务被取消
}
}
BroadcastReceiver 是 Android 中用于接收广播的组件。它通过监听系统广播或自定义广播来响应事件(如网络状态变化、电池电量变化等)。BroadcastReceiver
不会启动一个独立的线程,它只会在接收到广播时执行相应的代码。onReceive()
方法在接收到广播时调用。适用监听和响应系统广播,如网络连接变化、设备开关机、短信接收、系统更新等场景
public class NetworkChangeReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) {
// 处理网络变化
}
}
}
服务类型 | 生命周期 | 适用场景 | 优缺点 |
---|---|---|---|
普通 Service | onCreate() → onStartCommand() → onDestroy() | 短时间的后台任务 | 简单易用,但容易被系统杀死,适用于短时间的任务 |
前台 ForegroundService | onCreate() → onStartCommand() → onDestroy() | 长时间运行的任务,如播放音乐、导航 | 不容易被杀死,但需要显示通知,可能影响用户体验 |
JobService | onCreate() → onStartJob() → onStopJob() | 执行定期任务、延迟任务,或者需要满足某些条件的任务 | 适用于需要在特定条件下执行的任务,延迟执行,但不能精确控制执行时机 |
BroadcastReceiver | onReceive() | 响应广播事件,如网络状态变化、电池状态变化等 | 适合处理广播事件,不能执行长时间操作,执行时间受限制 |
总结
- Redis如果未使用Jemalloc无法开启主动的碎片回收,通过
redis-cli INFO memory | grep allocator
可查询内存分配器 - 启动Redis的方法
/usr/local/bin/redis-server ./redis.conf --daemonize yes
- 管理node模块时可以选择
nmp
或者pnmp
,不要混用,总的来说后者更优秀一点 - 管理Nextjs项目时最好吧
package.json
和package-lock.json
(pnpm-lock.yaml)都上传,便于安装出相同的运行环境 - 触发Vercel重新部署的命令
git commit --allow-empty -m"trigger redeployment"
- Android的服务有很多种,普通服务Service、前台服务ForegroundService,定时服务JobService等等
在自己的世界里独善其身,在别人的世界里顺其自然~