1774110743
This commit is contained in:
@@ -63,3 +63,5 @@
|
||||
5、建立客户跟进记录管理体系,支持对跟进情况的记录、查询、修改和删除。
|
||||
|
||||
6、建立自动化的预警机制,支持预警规则的自定义配置,支持查询、修改、删除预警规则,并能定时生成预警提醒,防范业务风险。
|
||||
|
||||
帮我看下现在 spring ai 底层的重试机制是怎么样的?会重试多少次?我怎么看到过 30 多次的日志。
|
||||
|
||||
@@ -216,6 +216,498 @@ export const User = (user: UserProps) => {
|
||||
|
||||
```
|
||||
|
||||
关于 TypeScript 定义 Props 类型:
|
||||
|
||||
```tsx
|
||||
type UserProps = {
|
||||
name: string
|
||||
age: number
|
||||
isVerified: boolean
|
||||
hobbies?: string[]
|
||||
}
|
||||
|
||||
export const User = (user: UserProps) => {
|
||||
const { name, age, isVerified, hobbies } = user // 这里是解构的意思。
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>{name}</h2>
|
||||
<p>Age: {age}</p>
|
||||
<p>校验: {isVerified ? '已验证' : '未验证'}</p>
|
||||
{hobbies && hobbies.length > 0 && <p>爱好: {hobbies.join(', ')}</p>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
事件处理和合成事件系统
|
||||
|
||||
这里相当于把函数当作了 Porps,此时实现了子组件决定函数的调用,但是父组件可以控制状态。
|
||||
|
||||
```tsx
|
||||
export const Button = () => {
|
||||
const handeClick = () => {
|
||||
alert('按钮触发了事件')
|
||||
}
|
||||
|
||||
return <button onClick={handeClick}>点击我</button>
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
这里我们自己实现一个父子组件函数通信的能力。这里相当于我们自己声明了一个类似 onClick 的子组件。
|
||||
|
||||
```tsx
|
||||
type LoginButtonProps = {
|
||||
onLogin: () => void
|
||||
}
|
||||
|
||||
export const LoginButton = ({ onLogin }: LoginButtonProps) => {
|
||||
return <button onClick={onLogin}>登录</button>
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
> 这里的时间对象不是原生浏览器对象,是一个合成事件。
|
||||
|
||||
useState
|
||||
|
||||
```tsx
|
||||
import { useState } from 'react'
|
||||
|
||||
const Counter = () => {
|
||||
const [count, setCount] = useState(0)
|
||||
const handelClick = () => {
|
||||
setCount(count + 1)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Counter</h1>
|
||||
<p>Count: {count}</p>
|
||||
<button onClick={handelClick}>增加</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Counter
|
||||
|
||||
```
|
||||
|
||||
这里 `setUser` 要用一个新的对象,否则话是不会生效的。
|
||||
|
||||
```tsx
|
||||
import { useState } from 'react'
|
||||
|
||||
const ErrorState = () => {
|
||||
const [user, setUser] = useState({ name: 'tianzhuo', age: 20 })
|
||||
const handelClick = () => {
|
||||
setUser({ ...user, age: user.age + 1 })
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>ErrorState</h1>
|
||||
<p>Name {user.name}</p>
|
||||
<p>Age {user.age}</p>
|
||||
<button onClick={handelClick}>Increment Age</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ErrorState
|
||||
|
||||
```
|
||||
|
||||
三元运算符
|
||||
|
||||
```tsx
|
||||
import { useState } from 'react'
|
||||
|
||||
export const Login = () => {
|
||||
const [isLogin, setIsLogin] = useState(false)
|
||||
return (
|
||||
<div>
|
||||
<h1>{isLogin ? 'Welcome Back!' : 'Please Login'}</h1>
|
||||
<button onClick={() => setIsLogin(!isLogin)}>Toggle Login</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
`&&` 标签显示问题
|
||||
|
||||
```tsx
|
||||
export const MaillBox = ({ messge }) => {
|
||||
return (
|
||||
<div>
|
||||
<h1>MailBox</h1>
|
||||
{messge.length > 0 && <h2>You have {messge.length} unread messages.</h2>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
代办
|
||||
|
||||
```tsx
|
||||
const todos = [
|
||||
{ id: 1, tesx: 'Learn React' },
|
||||
{ id: 2, tesx: 'Learn TypeScript' },
|
||||
{ id: 3, tesx: 'Build a React App' },
|
||||
]
|
||||
|
||||
export const TodoList = () => {
|
||||
return (
|
||||
<div>
|
||||
<h1>Todo List</h1>
|
||||
<ul className="li">
|
||||
{todos.map((todo) => (
|
||||
<li key={todo.id}>{todo.tesx}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## hook 生命周期
|
||||
|
||||
因为 StrictMode 的关系,这里会展示两次挂载。
|
||||
|
||||
同时如果依赖数组如果是空的话,只会渲染一次。
|
||||
|
||||
```tsx
|
||||
import { useState, useEffect } from 'react'
|
||||
|
||||
export const UserProfile = ({ userId }) => {
|
||||
const [userData, setUserData] = useState(null)
|
||||
|
||||
useEffect(() => {
|
||||
console.log('Fetching user data for userId:', userId)
|
||||
const timer = setTimeout(() => {
|
||||
setUserData({ name: 'John Doe', age: 30 })
|
||||
}, 1000)
|
||||
return () => {
|
||||
clearTimeout(timer)
|
||||
console.log('🧹 定时器已清理')
|
||||
}
|
||||
}, [userId])
|
||||
|
||||
if (!userData) {
|
||||
return <div>Loading...</div>
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<h1>User Profile</h1>
|
||||
<p>Name: {userData.name}</p>
|
||||
<p>Age: {userData.age}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
hook 的更新
|
||||
|
||||
这里相当与用 useState 触发了依赖数组的更新。只得关注的是每变化的时候,是先执行上一次的 clean。
|
||||
|
||||
```tsx
|
||||
import { useState, useEffect } from 'react'
|
||||
|
||||
export const UserProfile = () => {
|
||||
const [userId, setUserId] = useState(1) // ← 当前用户ID
|
||||
const [userData, setUserData] = useState(null)
|
||||
|
||||
// 模拟不同ID返回不同数据
|
||||
const mockUsers = {
|
||||
1: { name: '张三', age: 28 },
|
||||
2: { name: '李四', age: 35 },
|
||||
3: { name: '王五', age: 42 },
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
console.log(`🚀 开始请求 userId: ${userId}`)
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
setUserData(mockUsers[userId] || { name: '未知用户', age: 0 })
|
||||
console.log(`✅ 数据加载完成!当前用户: ${mockUsers[userId]?.name}`)
|
||||
}, 800)
|
||||
|
||||
// 清理函数(StrictMode 会展示)
|
||||
return () => {
|
||||
console.log(`🧹 清理上一次的定时器 (userId: ${userId})`)
|
||||
clearTimeout(timer)
|
||||
}
|
||||
}, [userId]) // ← 关键!依赖 userId
|
||||
|
||||
if (!userData) {
|
||||
return <div>加载中...</div>
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ padding: '20px', border: '1px solid #ccc' }}>
|
||||
<h1>用户资料(当前 ID: {userId})</h1>
|
||||
<p>姓名:{userData.name}</p>
|
||||
<p>年龄:{userData.age}</p>
|
||||
|
||||
<hr />
|
||||
<button onClick={() => setUserId(1)}>切换到张三 (ID=1)</button>
|
||||
<button onClick={() => setUserId(2)} style={{ marginLeft: '10px' }}>
|
||||
切换到李四 (ID=2)
|
||||
</button>
|
||||
<button onClick={() => setUserId(3)} style={{ marginLeft: '10px' }}>
|
||||
切换到王五 (ID=3)
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
hook 常见问题
|
||||
|
||||
1. 无限循环
|
||||
|
||||
```tsx
|
||||
import { useState, useEffect } from 'react'
|
||||
|
||||
export const BadExample = () => {
|
||||
const [count, setCount] = useState(0)
|
||||
|
||||
useEffect(() => {
|
||||
console.log(`🔄 Effect 执行了!当前 count = ${count}`)
|
||||
|
||||
// 这里更新了 count,而 count 又是依赖项
|
||||
setCount(count + 1) // ← 罪魁祸首!
|
||||
}, [count]) // ← 依赖了 count
|
||||
|
||||
return (
|
||||
<div style={{ padding: '20px', border: '2px solid red' }}>
|
||||
<h1>无限循环演示</h1>
|
||||
<p>当前 count: {count}</p>
|
||||
<p style={{ color: 'red' }}>看控制台!页面会疯狂卡顿,count 一直加!</p>
|
||||
<button onClick={() => setCount(0)}>重置(也没用)</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
2. 忘记放依赖项。
|
||||
|
||||
useRef
|
||||
|
||||
这里放的是 input
|
||||
|
||||
```tsx
|
||||
import { useRef, useEffect } from 'react'
|
||||
|
||||
export const RefExample = () => {
|
||||
const inputRef = useRef(null)
|
||||
|
||||
useEffect(() => {
|
||||
inputRef.current?.focus()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>useRef 示例:自动聚焦输入框</h1>
|
||||
<input ref={inputRef} type="text" placeholder="focus"></input>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
动态数据存储
|
||||
|
||||
```tsx
|
||||
import { useRef, useState, useEffect } from 'react'
|
||||
|
||||
export const Search = () => {
|
||||
const [query, setQuery] = useState('')
|
||||
const timeOutRef = useRef(null)
|
||||
|
||||
useEffect(() => {
|
||||
timeOutRef.current = setTimeout(() => {
|
||||
console.log('搜索:', query)
|
||||
}, 500)
|
||||
|
||||
return () => {
|
||||
console.log('清除上一次的定时器')
|
||||
clearTimeout(timeOutRef.current)
|
||||
}
|
||||
}, [query])
|
||||
|
||||
return (
|
||||
<input value={query} onChange={(e) => setQuery(e.target.value)}></input>
|
||||
)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## 第二部分
|
||||
|
||||
新老表单
|
||||
|
||||
```tsx
|
||||
export const OldForm = () => {
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault() // 这里是为了阻止表单提交默认的动作。
|
||||
const formData = new FormData(e.target)
|
||||
const name = formData.get('name')
|
||||
console.log(name)
|
||||
}
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<input type="text" name="name" />
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
```tsx
|
||||
export const NewForm = () => {
|
||||
const handleSubmit = async (formdata: FormData) => { // 异步操作
|
||||
const name = formdata.get('name')
|
||||
console.log(name)
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||||
}
|
||||
|
||||
return (
|
||||
<form action={handleSubmit}>
|
||||
<input type="text" name="name" />
|
||||
<button type="submit">Submit</button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
useActionState,这个我理解其实是一个对历史功能的封装优化。简化了自己需要声明很多的 state。
|
||||
|
||||
```tsx
|
||||
import { useActionState } from 'react'
|
||||
|
||||
export const UseActionState = ({ productId }) => {
|
||||
const addToCart = async (previousState, formData) => {
|
||||
const quantity = formData.get('quantity')
|
||||
console.log('Added to cart')
|
||||
const result = await addToCart1(productId, quantity)
|
||||
if (result.success) {
|
||||
return { success: true, message: 'Product added to cart successfully!' }
|
||||
} else {
|
||||
return { success: false, message: 'Failed to add product to cart.' }
|
||||
}
|
||||
}
|
||||
|
||||
const addToCart1 = (productId, quantity) => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve({ success: true })
|
||||
}, 1000)
|
||||
})
|
||||
}
|
||||
|
||||
const [state, submitAction, isPending] = useActionState(addToCart, null)
|
||||
|
||||
return (
|
||||
<form action={submitAction}>
|
||||
<input type="number" name="quantity" defaultValue={1} min={1} />
|
||||
<button type="submit" disabled={isPending}>
|
||||
{isPending ? 'Adding...' : 'Add to Cart'}
|
||||
</button>
|
||||
{state?.message && <p>{state.message}</p>}
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
```
|
||||
|
||||
useformState
|
||||
|
||||
可使用 useFormState 获取父组件的表单状态。
|
||||
|
||||
```tsx
|
||||
import { useFormStatus } from 'react-dom'
|
||||
|
||||
const SubButton = () => {
|
||||
const { pending } = useFormStatus()
|
||||
return (
|
||||
<button type="subbmit" disabled={pending}>
|
||||
{pending ? 'Submitting...' : 'Submit'}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
export const NewForm = () => {
|
||||
const handeSubmit = async (formData) => {
|
||||
const name = formData.get('name')
|
||||
console.log('Form submitted with name:', name)
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000))
|
||||
console.log('Form submission completed')
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<form action={handeSubmit}>
|
||||
<input type="text" name="name" />
|
||||
<SubButton />
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
```
|
||||
|
||||
use
|
||||
|
||||
这里的一个想法是实现了层级 fallback
|
||||
|
||||
```tsx
|
||||
import React, { Suspense, use } from 'react'
|
||||
import { ErrorBoundary } from 'react-error-boundary'
|
||||
|
||||
function fetchUser() {
|
||||
return new Promise<{ name: string }>((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
const success = false
|
||||
|
||||
if (success) {
|
||||
resolve({ name: '张三' })
|
||||
} else {
|
||||
reject(new Error('获取用户失败'))
|
||||
}
|
||||
}, 2000)
|
||||
})
|
||||
}
|
||||
|
||||
function UserInfo({ userPromise }: { userPromise: Promise<{ name: string }> }) {
|
||||
const user = use(userPromise)
|
||||
return <h2>用户名:{user.name}</h2>
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
const userPromise = fetchUser()
|
||||
|
||||
return (
|
||||
<ErrorBoundary fallback={<p>加载失败了</p>}>
|
||||
<Suspense fallback={<p>用户加载中...</p>}>
|
||||
<UserInfo userPromise={userPromise} />
|
||||
</Suspense>
|
||||
</ErrorBoundary>
|
||||
)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
# CSS
|
||||
|
||||
顶级作用域
|
||||
|
||||
@@ -0,0 +1,787 @@
|
||||
# Redis 8.6.1 标准安装文档
|
||||
|
||||
适用系统:**Anolis OS 8.6 x86_64**
|
||||
|
||||
---
|
||||
|
||||
# 1. 安装规划
|
||||
|
||||
本安装文档采用如下标准目录:
|
||||
|
||||
| 项目 | 路径 |
|
||||
| ---------- | ------------------------ |
|
||||
| 安装包目录 | `/usr/local/src` |
|
||||
| Redis 安装目录 | `/usr/local/redis-8.6.1` |
|
||||
| 软链接 | `/usr/local/redis` |
|
||||
| 配置目录 | `/etc/redis` |
|
||||
| 数据目录 | `/data/redis` |
|
||||
| 日志目录 | `/var/log/redis` |
|
||||
| 运行用户 | `redis` |
|
||||
| 服务名 | `redis` |
|
||||
| 监听端口 | `6379` |
|
||||
|
||||
---
|
||||
|
||||
# 2. 前置准备
|
||||
|
||||
## 2.1 切换 root 用户
|
||||
|
||||
```bash
|
||||
sudo -i
|
||||
```
|
||||
|
||||
## 2.2 关闭或放通防火墙端口(如有需要)
|
||||
|
||||
如果 Redis 仅本机访问,可以不开放 6379。
|
||||
|
||||
若需要远程访问:
|
||||
|
||||
```bash
|
||||
firewall-cmd --permanent --add-port=6379/tcp
|
||||
firewall-cmd --reload
|
||||
```
|
||||
|
||||
查看:
|
||||
|
||||
```bash
|
||||
firewall-cmd --list-ports
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 3. 安装编译依赖
|
||||
|
||||
Anolis OS 8.6 与 RHEL/CentOS 8 系兼容,可使用 `dnf`。
|
||||
|
||||
```bash
|
||||
dnf -y install gcc gcc-c++ make tar wget perl systemd-devel
|
||||
```
|
||||
|
||||
建议再安装常用工具:
|
||||
|
||||
```bash
|
||||
dnf -y install vim net-tools lsof
|
||||
```
|
||||
|
||||
验证 gcc:
|
||||
|
||||
```bash
|
||||
gcc --version
|
||||
make --version
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 4. 上传并解压安装包
|
||||
|
||||
假设你已经将 `redis-8.6.1.tar.gz` 上传到 `/usr/local/src/`
|
||||
|
||||
```bash
|
||||
cd /usr/local/src
|
||||
ls -lh redis-8.6.1.tar.gz
|
||||
```
|
||||
|
||||
解压:
|
||||
|
||||
```bash
|
||||
tar -xzf redis-8.6.1.tar.gz
|
||||
cd redis-8.6.1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 5. 编译 Redis
|
||||
|
||||
执行编译:
|
||||
|
||||
```bash
|
||||
make -j$(nproc)
|
||||
```
|
||||
|
||||
安装到指定目录:
|
||||
|
||||
```bash
|
||||
make PREFIX=/usr/local/redis-8.6.1 install
|
||||
```
|
||||
|
||||
创建软链接:
|
||||
|
||||
```bash
|
||||
ln -sfn /usr/local/redis-8.6.1 /usr/local/redis
|
||||
```
|
||||
|
||||
验证:
|
||||
|
||||
```bash
|
||||
/usr/local/redis/bin/redis-server -v
|
||||
/usr/local/redis/bin/redis-cli -v
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 6. 创建运行用户和目录
|
||||
|
||||
## 6.1 创建 redis 用户
|
||||
|
||||
```bash
|
||||
id redis >/dev/null 2>&1 || useradd -r -s /sbin/nologin redis
|
||||
```
|
||||
|
||||
## 6.2 创建配置、数据、日志目录
|
||||
|
||||
```bash
|
||||
mkdir -p /etc/redis
|
||||
mkdir -p /data/redis
|
||||
mkdir -p /var/log/redis
|
||||
```
|
||||
|
||||
## 6.3 设置权限
|
||||
|
||||
```bash
|
||||
chown -R redis:redis /data/redis
|
||||
chown -R redis:redis /var/log/redis
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 7. 生成 Redis 配置文件
|
||||
|
||||
从源码目录复制默认配置:
|
||||
|
||||
```bash
|
||||
cp /usr/local/src/redis-8.6.1/redis.conf /etc/redis/redis.conf
|
||||
```
|
||||
|
||||
备份一份:
|
||||
|
||||
```bash
|
||||
cp /etc/redis/redis.conf /etc/redis/redis.conf.bak
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 8. 修改 Redis 配置
|
||||
|
||||
编辑配置文件:
|
||||
|
||||
```bash
|
||||
vim /etc/redis/redis.conf
|
||||
```
|
||||
|
||||
建议修改为以下关键项。
|
||||
|
||||
---
|
||||
|
||||
## 8.1 基础配置
|
||||
|
||||
找到并修改:
|
||||
|
||||
```conf
|
||||
bind 0.0.0.0
|
||||
protected-mode yes
|
||||
port 6379
|
||||
tcp-backlog 511
|
||||
timeout 0
|
||||
tcp-keepalive 300
|
||||
daemonize no
|
||||
supervised systemd
|
||||
pidfile /var/run/redis_6379.pid
|
||||
loglevel notice
|
||||
logfile /var/log/redis/redis.log
|
||||
databases 16
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- `bind 0.0.0.0`:允许所有地址访问
|
||||
如果你只允许本机访问,建议改成:
|
||||
|
||||
```conf
|
||||
bind 127.0.0.1
|
||||
```
|
||||
|
||||
- `protected-mode yes`:建议保持开启
|
||||
- `daemonize no`:使用 systemd 启动时必须为 `no`
|
||||
- `supervised systemd`:推荐 systemd 管理
|
||||
|
||||
---
|
||||
|
||||
## 8.2 数据目录配置
|
||||
|
||||
修改:
|
||||
|
||||
```conf
|
||||
dir /data/redis
|
||||
dbfilename dump.rdb
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8.3 设置密码
|
||||
|
||||
### 方法一:兼容常见方式,使用 `requirepass`
|
||||
|
||||
在配置文件中加入或修改:
|
||||
|
||||
```conf
|
||||
requirepass 5sTb7fHFbsYl6KmI6pvC^XMw!7Y^Pbc1
|
||||
```
|
||||
|
||||
> 这是你最关心的“设置密码”方式,客户端连接时必须认证。
|
||||
|
||||
---
|
||||
|
||||
## 8.4 建议配置 master 默认认证密码
|
||||
|
||||
有些版本推荐 ACL 方式,生产上也可以同时设置默认用户密码:
|
||||
|
||||
```conf
|
||||
aclfile /etc/redis/users.acl
|
||||
```
|
||||
|
||||
然后创建 ACL 文件:
|
||||
|
||||
```bash
|
||||
cat >/etc/redis/users.acl <<'EOF'
|
||||
user default on >Redis@2025#Strong ~* &* +@all
|
||||
EOF
|
||||
```
|
||||
|
||||
设置权限:
|
||||
|
||||
```bash
|
||||
chown redis:redis /etc/redis/users.acl
|
||||
chmod 640 /etc/redis/users.acl
|
||||
```
|
||||
|
||||
> 如果你使用了 ACL 文件,推荐**不要同时混乱使用多套密码策略**。
|
||||
> 对于标准简单部署:
|
||||
> - **只用 `requirepass`** 就够了
|
||||
> - 如果你明确要 ACL 管理,就用 `aclfile`
|
||||
|
||||
---
|
||||
|
||||
## 8.5 持久化建议
|
||||
|
||||
### RDB 快照
|
||||
|
||||
建议保留:
|
||||
|
||||
```conf
|
||||
save 900 1
|
||||
save 300 10
|
||||
save 60 10000
|
||||
```
|
||||
|
||||
### AOF
|
||||
|
||||
建议开启:
|
||||
|
||||
```conf
|
||||
appendonly yes
|
||||
appendfilename "appendonly.aof"
|
||||
appendfsync everysec
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8.6 内存策略建议
|
||||
|
||||
根据你的机器规格(8 vCPU,通常中小型实例),如果 Redis 不是独占整机,建议先限制最大内存。
|
||||
|
||||
例如:
|
||||
|
||||
```conf
|
||||
maxmemory 2gb
|
||||
maxmemory-policy allkeys-lru
|
||||
```
|
||||
|
||||
> 如果这台机器专门跑 Redis,可按实际内存再调整。
|
||||
> 你没有提供内存大小,所以文档里给一个稳妥示例值。
|
||||
|
||||
---
|
||||
|
||||
# 9. 配置文件示例(可直接替换)
|
||||
|
||||
下面是一份可直接使用的 `/etc/redis/redis.conf` 核心示例:
|
||||
|
||||
```conf
|
||||
bind 0.0.0.0
|
||||
protected-mode yes
|
||||
port 6379
|
||||
tcp-backlog 511
|
||||
timeout 0
|
||||
tcp-keepalive 300
|
||||
|
||||
daemonize no
|
||||
supervised systemd
|
||||
pidfile /var/run/redis_6379.pid
|
||||
|
||||
loglevel notice
|
||||
logfile /var/log/redis/redis.log
|
||||
|
||||
databases 16
|
||||
|
||||
dir /data/redis
|
||||
dbfilename dump.rdb
|
||||
|
||||
save 900 1
|
||||
save 300 10
|
||||
save 60 10000
|
||||
|
||||
appendonly yes
|
||||
appendfilename "appendonly.aof"
|
||||
appendfsync everysec
|
||||
|
||||
requirepass Redis@2025#Strong
|
||||
|
||||
maxmemory 2gb
|
||||
maxmemory-policy allkeys-lru
|
||||
```
|
||||
|
||||
> 请把密码 `Redis@2025#Strong` 改成你自己的强密码。
|
||||
|
||||
---
|
||||
|
||||
# 10. 配置 systemd 服务
|
||||
|
||||
创建 systemd 服务文件:
|
||||
|
||||
```bash
|
||||
cat >/etc/systemd/system/redis.service <<'EOF'
|
||||
[Unit]
|
||||
Description=Redis In-Memory Data Store
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
User=redis
|
||||
Group=redis
|
||||
ExecStart=/usr/local/redis/bin/redis-server /etc/redis/redis.conf
|
||||
ExecStop=/usr/local/redis/bin/redis-cli -a '5sTb7fHFbsYl6KmI6pvC^XMw!7Y^Pbc1' shutdown
|
||||
Type=notify
|
||||
TimeoutStopSec=30
|
||||
Restart=always
|
||||
LimitNOFILE=65535
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
```
|
||||
|
||||
> 注意:这里 `ExecStop` 中的密码要和 `redis.conf` 的 `requirepass` 一致。
|
||||
> 如果你修改了密码,这里也要同步修改。
|
||||
|
||||
---
|
||||
|
||||
# 11. 系统内核参数优化
|
||||
|
||||
生产环境建议设置 `vm.overcommit_memory`。
|
||||
|
||||
临时生效:
|
||||
|
||||
```bash
|
||||
sysctl vm.overcommit_memory=1
|
||||
```
|
||||
|
||||
永久生效:
|
||||
|
||||
```bash
|
||||
echo 'vm.overcommit_memory = 1' >/etc/sysctl.d/99-redis.conf
|
||||
sysctl --system
|
||||
```
|
||||
|
||||
关闭透明大页(建议):
|
||||
|
||||
临时关闭:
|
||||
|
||||
```bash
|
||||
echo never > /sys/kernel/mm/transparent_hugepage/enabled
|
||||
echo never > /sys/kernel/mm/transparent_hugepage/defrag
|
||||
```
|
||||
|
||||
如需开机自动关闭,可写入 rc.local 或 systemd 脚本。
|
||||
|
||||
---
|
||||
|
||||
# 12. 启动 Redis
|
||||
|
||||
重新加载 systemd:
|
||||
|
||||
```bash
|
||||
systemctl daemon-reload
|
||||
```
|
||||
|
||||
设置开机自启:
|
||||
|
||||
```bash
|
||||
systemctl enable redis
|
||||
```
|
||||
|
||||
启动服务:
|
||||
|
||||
```bash
|
||||
systemctl start redis
|
||||
```
|
||||
|
||||
查看状态:
|
||||
|
||||
```bash
|
||||
systemctl status redis --no-pager -l
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 13. 验证安装和密码
|
||||
|
||||
## 13.1 查看监听端口
|
||||
|
||||
```bash
|
||||
ss -lntp | grep 6379
|
||||
```
|
||||
|
||||
## 13.2 本地连接测试
|
||||
|
||||
```bash
|
||||
/usr/local/redis/bin/redis-cli
|
||||
```
|
||||
|
||||
执行:
|
||||
|
||||
```bash
|
||||
ping
|
||||
```
|
||||
|
||||
如果设置了密码,会返回:
|
||||
|
||||
```text
|
||||
(error) NOAUTH Authentication required.
|
||||
```
|
||||
|
||||
认证:
|
||||
|
||||
```bash
|
||||
auth Redis@2025#Strong
|
||||
```
|
||||
|
||||
再执行:
|
||||
|
||||
```bash
|
||||
ping
|
||||
```
|
||||
|
||||
返回:
|
||||
|
||||
```text
|
||||
PONG
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 13.3 一条命令带密码连接
|
||||
|
||||
```bash
|
||||
/usr/local/redis/bin/redis-cli -a 'Redis@2025#Strong'
|
||||
```
|
||||
|
||||
测试:
|
||||
|
||||
```bash
|
||||
set test hello
|
||||
get test
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 14. 设置环境变量(可选)
|
||||
|
||||
为了直接使用 `redis-cli`、`redis-server` 命令:
|
||||
|
||||
```bash
|
||||
cat >/etc/profile.d/redis.sh <<'EOF'
|
||||
export PATH=/usr/local/redis/bin:$PATH
|
||||
EOF
|
||||
source /etc/profile.d/redis.sh
|
||||
```
|
||||
|
||||
验证:
|
||||
|
||||
```bash
|
||||
redis-cli -v
|
||||
redis-server -v
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 15. 常用运维命令
|
||||
|
||||
## 启动
|
||||
|
||||
```bash
|
||||
systemctl start redis
|
||||
```
|
||||
|
||||
## 停止
|
||||
|
||||
```bash
|
||||
systemctl stop redis
|
||||
```
|
||||
|
||||
## 重启
|
||||
|
||||
```bash
|
||||
systemctl restart redis
|
||||
```
|
||||
|
||||
## 查看状态
|
||||
|
||||
```bash
|
||||
systemctl status redis
|
||||
```
|
||||
|
||||
## 查看日志
|
||||
|
||||
```bash
|
||||
tail -f /var/log/redis/redis.log
|
||||
```
|
||||
|
||||
## 登录 Redis
|
||||
|
||||
```bash
|
||||
redis-cli -a 'Redis@2025#Strong'
|
||||
```
|
||||
|
||||
## 查看配置
|
||||
|
||||
```bash
|
||||
redis-cli -a 'Redis@2025#Strong' CONFIG GET requirepass
|
||||
redis-cli -a 'Redis@2025#Strong' INFO
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 16. 修改 Redis 密码的方法
|
||||
|
||||
如果后续要改密码,有两种方式。
|
||||
|
||||
---
|
||||
|
||||
## 方法一:修改配置文件后重启
|
||||
|
||||
编辑:
|
||||
|
||||
```bash
|
||||
vim /etc/redis/redis.conf
|
||||
```
|
||||
|
||||
修改:
|
||||
|
||||
```conf
|
||||
requirepass NewStrongPassword@456
|
||||
```
|
||||
|
||||
然后同步修改 systemd 文件里的 `ExecStop` 密码:
|
||||
|
||||
```bash
|
||||
vim /etc/systemd/system/redis.service
|
||||
```
|
||||
|
||||
修改:
|
||||
|
||||
```conf
|
||||
ExecStop=/usr/local/redis/bin/redis-cli -a 'NewStrongPassword@456' shutdown
|
||||
```
|
||||
|
||||
重载并重启:
|
||||
|
||||
```bash
|
||||
systemctl daemon-reload
|
||||
systemctl restart redis
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 方法二:运行时修改(临时 + 持久化)
|
||||
|
||||
先认证登录:
|
||||
|
||||
```bash
|
||||
redis-cli -a '旧密码'
|
||||
```
|
||||
|
||||
执行:
|
||||
|
||||
```bash
|
||||
CONFIG SET requirepass NewStrongPassword@456
|
||||
CONFIG REWRITE
|
||||
```
|
||||
|
||||
> 如果你用了 ACL,密码修改方式会不同。
|
||||
|
||||
---
|
||||
|
||||
# 17. 卸载方法
|
||||
|
||||
停止服务:
|
||||
|
||||
```bash
|
||||
systemctl stop redis
|
||||
systemctl disable redis
|
||||
```
|
||||
|
||||
删除服务文件:
|
||||
|
||||
```bash
|
||||
rm -f /etc/systemd/system/redis.service
|
||||
systemctl daemon-reload
|
||||
```
|
||||
|
||||
删除安装目录:
|
||||
|
||||
```bash
|
||||
rm -rf /usr/local/redis-8.6.1
|
||||
rm -f /usr/local/redis
|
||||
```
|
||||
|
||||
删除配置和数据(谨慎):
|
||||
|
||||
```bash
|
||||
rm -rf /etc/redis
|
||||
rm -rf /data/redis
|
||||
rm -rf /var/log/redis
|
||||
```
|
||||
|
||||
删除用户:
|
||||
|
||||
```bash
|
||||
userdel redis
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 18. 推荐的密码规范
|
||||
|
||||
建议 Redis 密码满足:
|
||||
|
||||
- 长度不少于 16 位
|
||||
- 包含:
|
||||
- 大写字母
|
||||
- 小写字母
|
||||
- 数字
|
||||
- 特殊字符
|
||||
- 不使用弱密码,如:
|
||||
- `123456`
|
||||
- `redis123`
|
||||
- `admin@123`
|
||||
|
||||
示例强密码:
|
||||
|
||||
```text
|
||||
Rds@8.6.1_2025#X9pL
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 19. 最简安装命令汇总版
|
||||
|
||||
如果你想快速执行,下面是精简版流程。
|
||||
|
||||
## 19.1 安装依赖
|
||||
|
||||
```bash
|
||||
dnf -y install gcc gcc-c++ make tar wget perl vim net-tools lsof
|
||||
```
|
||||
|
||||
## 19.2 编译安装
|
||||
|
||||
```bash
|
||||
cd /usr/local/src
|
||||
tar -xzf redis-8.6.1.tar.gz
|
||||
cd redis-8.6.1
|
||||
make -j$(nproc)
|
||||
make PREFIX=/usr/local/redis-8.6.1 install
|
||||
ln -sfn /usr/local/redis-8.6.1 /usr/local/redis
|
||||
```
|
||||
|
||||
## 19.3 创建用户和目录
|
||||
|
||||
```bash
|
||||
useradd -r -s /sbin/nologin redis
|
||||
mkdir -p /etc/redis /data/redis /var/log/redis
|
||||
chown -R redis:redis /data/redis /var/log/redis
|
||||
cp /usr/local/src/redis-8.6.1/redis.conf /etc/redis/redis.conf
|
||||
```
|
||||
|
||||
## 19.4 修改配置
|
||||
|
||||
```bash
|
||||
cat >/etc/redis/redis.conf <<'EOF'
|
||||
bind 0.0.0.0
|
||||
protected-mode yes
|
||||
port 6379
|
||||
tcp-backlog 511
|
||||
timeout 0
|
||||
tcp-keepalive 300
|
||||
daemonize no
|
||||
supervised systemd
|
||||
pidfile /var/run/redis_6379.pid
|
||||
loglevel notice
|
||||
logfile /var/log/redis/redis.log
|
||||
databases 16
|
||||
dir /data/redis
|
||||
dbfilename dump.rdb
|
||||
save 900 1
|
||||
save 300 10
|
||||
save 60 10000
|
||||
appendonly yes
|
||||
appendfilename "appendonly.aof"
|
||||
appendfsync everysec
|
||||
requirepass Rds@8.6.1_2025#X9pL
|
||||
maxmemory 2gb
|
||||
maxmemory-policy allkeys-lru
|
||||
EOF
|
||||
```
|
||||
|
||||
## 19.5 systemd
|
||||
|
||||
```bash
|
||||
cat >/etc/systemd/system/redis.service <<'EOF'
|
||||
[Unit]
|
||||
Description=Redis In-Memory Data Store
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
User=redis
|
||||
Group=redis
|
||||
ExecStart=/usr/local/redis/bin/redis-server /etc/redis/redis.conf
|
||||
ExecStop=/usr/local/redis/bin/redis-cli -a 'Rds@8.6.1_2025#X9pL' shutdown
|
||||
Type=notify
|
||||
Restart=always
|
||||
LimitNOFILE=65535
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
```
|
||||
|
||||
## 19.6 启动
|
||||
|
||||
```bash
|
||||
echo 'vm.overcommit_memory = 1' >/etc/sysctl.d/99-redis.conf
|
||||
sysctl --system
|
||||
systemctl daemon-reload
|
||||
systemctl enable redis
|
||||
systemctl start redis
|
||||
systemctl status redis --no-pager -l
|
||||
```
|
||||
|
||||
## 19.7 测试
|
||||
|
||||
```bash
|
||||
/usr/local/redis/bin/redis-cli -a 'Rds@8.6.1_2025#X9pL' ping
|
||||
```
|
||||
|
||||
返回:
|
||||
|
||||
```text
|
||||
PONG
|
||||
```
|
||||
Reference in New Issue
Block a user