redis一主一从哨兵模式_kafka主从复制

redis一主一从哨兵模式_kafka主从复制文章目录Redis持久化RDB(Redis DataBase)AOF(Append Only File)Redis持久化Redis是内存数据库,如果不见内存中的数据库状态保存到磁盘,那么一旦服务器进程退出,服务器中的数据库状态也会消失。所以Redis提供了持久化功能RDB(Redis DataBase)什么是RDB:在指定的时间间隔内将内存中的数据集快照写入内存, 也就是行话讲的Snapshot快照,他恢复时是将快照文件直接读到内存中。Redis会单独创建一个子进程来持节话,会先将数据写入到一个

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

Redis持久化

Redis是内存数据库,如果不见内存中的数据库状态保存到磁盘,那么一旦服务器进程退出,服务器中的数据库状态也会消失。所以Redis提供了持久化功能

RDB(Redis DataBase)

什么是RDB:
在指定的时间间隔内将内存中的数据集快照写入内存, 也就是行话讲的Snapshot快照,他恢复时是将快照文件直接读到内存中。
Redis会单独创建一个子进程来持节话,会先将数据写入到一个临时文件中,待持久化过程都结束了。再用这个临时文件替换上次持久化好的文件。整个过程中,主进程都不参与IO操作,这就极高的提升了性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方实要比AOF方实更加高效。RDB缺点是最后一次持久化后的数据可能丢失。
等下一次启动Redis的时候,会将持久化文件恢复到内存
rdb配置

save 60 5 //60s内修改5个值就会自动执行持久化 
dbfilename dump.rdb	//文件
# 都是再配置文件中进行的配置

持久化触发
1)手动触发
手动触发持久化的操作有两个:save 和 bgsave ,它们主要区别体现在:是否阻塞 Redis 主线程的执行。

  1. save 命令
    在客户端中执行 save 命令,就会触发 Redis 的持久化,但同时也是使 Redis 处于阻塞状态,直到 RDB 持久化完成,才会响应其他客户端发来的命令,
  2. bgsave 命令
    bgsave(background save)既后台保存的意思, 它和 save 命令最大的区别就是 bgsave 会 fork() 一个子进程来执行持久化,整个过程中只有在 fork() 子进程时有短暂的阻塞,当子进程被创建之后,Redis 的主进程就可以响应其他客户端的请求了。

2)自动触发
说完了 RDB 的手动触发方式,下面来看如何自动触发 RDB 持久化?RDB 自动持久化主要来源于以下几种情况

  1. save的规则满足的情况下,会自动生成rdb文件
  2. 执行flushall命令,也会生成rdb文件
  3. 退出redis,也会产生rdb文件
  4. 主从同步触发:在 Redis 主从复制中,当从节点执行全量复制操作时,主节点会执行 bgsave 命令,并将 RDB 文件发送给从节点,该过程会自动触发 Redis 持久化。

系统如果获得rdb文件?
只要将rdb文件放在我们redis启动目录就可以,redis启动的时候会自动检查dump.rdb恢复其中的数据
查看redis启动目录

127.0.0.1:6379> config get dir
1) "dir"
2) "/var/lib/redis"		//rdb文件应该存在这个目录下面

优点:

  • 适合大规模数据恢复
  • 对数据的完整性要求不高
    缺点
  • 需要一定的事件间隔操作,如果redis意外宕机了,这个最后一次修改数据就没有了
  • fork进程的时候,会占用一定的内存空间

AOF(Append Only File)

将我们的所有命令都记下来,恢复的时候把我们的命令全部都重新执行一边。
以日志的形式来记录每个写操作,将Redis执行过的所有指令记录下来(读操作不记录),之追加文件但不可以改写文件,Redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容写执行从前到后执行一次以恢复内存
OF 重写函数会进行大量的写入操作,调用该函数的线程将被长时间阻塞,所以 Redis 在子进程中执行 AOF 重写操作。

  • 子进程进行 AOF 重写期间,Redis 进程可以继续处理客户端命令请求。
  • 子进程带有父进程的内存数据拷贝副本,在不适用锁的情况下,也可以保证数据的安全性。
    但是,在子进程进行 AOF 重启期间,Redis接收客户端命令,会对现有数据库状态进行修改,从而导致数据当前状态和 重写后的 AOF 文件所保存的数据库状态不一致。

为此,Redis 设置了一个 AOF 重写缓冲区,这个缓冲区在服务器创建子进程之后开始使用,当 Redis 执行完一个写命令之后,它会同时将这个写命令发送给 AOF 缓冲区和 AOF 重写缓冲区。

当子进程完成 AOF 重写工作之后,它会向父进程发送一个信号,父进程在接收到该信号之后,会调用一个信号处理函数,并执行以下工作:

  • 将 AOF 重写缓冲区中的所有内容写入到新的 AOF 文件中,保证新 AOF 文件保存的数据库状态和服务器当前状态一致。
  • 对新的 AOF 文件进行改名,原子地覆盖现有 AOF 文件,完成新旧文件的替换
  • 继续处理客户端请求命令。

AOF保存到appendonly,aof文件

# redis.conf
appendonly yes		//手动开启aof
appendfilename 'appendonly aof'	//生成的文件名
appendsync everysec		//每秒都会追加一次
appendsync always 	//每次修改都记录
appendsync no		//不执行 sync 这个时候操作系统同步数据
auto-aof-rewrite-percenttage = 100	//配置较前一个aof文件大小增长的百分比
auto-aof-rewrite-min-size 64mb	//配置触发aof重写的aof的最小的大小。

....配置

ubuntu查找aop文件所在目录

127.0.0.1:6379> config get dir
1) "dir"
2) "/var/lib/redis"		//aop文件在这个目录下
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> set k2 v2
OK
127.0.0.1:6379> set k3 v3
OK

appendonly.aof文件如下
在这里插入图片描述
现在我们破坏先删除rdb文件,再破坏aop文件。
下图为随机破坏aop文件
在这里插入图片描述
然后启动redis发现不能正常启动
在这里插入图片描述
这个时候我们需要redis-check-aof文件修复 appendonly.aop文件

redis-check-aof --fix appendonly,aof		//修复aof文件,再次启动就是正常启动

优点

  • 可以设置每一次修改都同步,文件的完整性更好
  • 可以设置每秒同步一次,可能会丢失一秒的数据
  • 也可以设置从不同步,这样效率最高
    缺点
  • 相对于数据文件来说,aof远远大于rdb,修复速度也比rdb慢
  • Aof运行效率比rdb慢,一般情况下都选择rdb

注意
Redis服务器重启时会优先加载aof文件来恢复数据

Redis发布订阅

Redis发布订阅pub/sub是一种消息通信模式:发布者(pub)发送消息,订阅者(sub)接收消息。
应用场景微信,微博,关注系统!
订阅/发布消息:
在这里插入图片描述
相关命令
在这里插入图片描述
ubuntu测试

# 客户端1订阅一个
127.0.0.1:6379> PSUBSCRIBE ccmessageccmessage频道
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "ccmessage"
3) (integer) 1
# 客户端2发送一个消息
127.0.0.1:6379> PUBLISH ccmessage 'hello cc'
(integer) 1
# 客户端1接受到消息
1) "pmessage"
2) "ccmessage"
3) "ccmessage"
4) "hello cc"

Redis是用c实现的,通过publish,subsscribe和psubscribe等命令发布和订阅功能
通过subscribe命令订阅某频道后,redis-server里面维护了一个字典,字典的键就是一个个频道,而字典的值就是一个链表,链表中保存在所有订阅这个channel的客户端,subscribe命令的关键就是将客户端添加到给定channel的订阅系统中。
通过Publish命令向订阅者发送信息,redis-server会使用给定的频道作为键,在它所维护的频道字典中朝朝记录了订阅这个频道的所有客户端的链表,将消息发布给所有订阅者。

Redis 主从复制

主从复制,读写分离! 80% 的情况下都是在进行读操作!减缓服务器的压力!架构中经常使用! 一主二从!只要在公司中,主从复制就是必须要使用的,因为在真实的项目中不可能单机使用Redis!

概念
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(Master/Leader),后者称为从节点(Slave/Follower), 数据的复制是单向的!只能由主节点复制到从节点(主节点以写为主、从节点以读为主)。

默认情况下,每台Redis服务器都是主节点,一个主节点可以有0个或者多个从节点,但每个从节点只能由一个主节点。
作用

  • 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余的方式。
  • 故障恢复:当主节点故障时,从节点可以暂时替代主节点提供服务,是一种服务冗余的方式
  • 负载均衡:在主从复制的基础上,配合读写分离,由主节点进行写操作,从节点进行读操作,分担服务器的负载;尤其是在多读少写的场景下,通过多个从节点分担负载,提高并发量。
  • 高可用基石:主从复制还是哨兵和集群能够实施的基础。
# 查看当前库的信息
127.0.0.1:6379> info replication
# Replication
role:master	# 主机
connected_slaves:0	//从机数量
master_repl_offset:0
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0


搭建集群环境
赋值3个配置文件redis.conf,然后修改相应信息

  1. 端口
  2. pid名字
  3. logfile
  4. dbfilename

启动三个Redis服务器

root@ubuntu:/etc/redis# redis-server redis.conf 
root@ubuntu:/etc/redis# redis-server redis-1.conf 
root@ubuntu:/etc/redis# redis-server redis-2.conf 
root@ubuntu:/etc/redis# ps -ef | grep redis
root       2853   1568  0 18:59 ?        00:00:00 redis-server 127.0.0.1:6379
root       2859   1568  0 18:59 ?        00:00:00 redis-server 127.0.0.1:6380
root       2863   1568  0 18:59 ?        00:00:00 redis-server 127.0.0.1:6381

配置从机,使得6379为主机,6380,6381的为从机

127.0.0.1:6380> SLAVEOF 127.0.0.1 6379
OK
# 查看从机信息
127.0.0.1:6380> info replication
# Replication
role:slave		// 这是一个从机
master_host:127.0.0.1		//主机地址
master_port:6379		//主机端口号
master_link_status:up
master_last_io_seconds_ago:3
master_sync_in_progress:0
slave_repl_offset:29
slave_priority:100
slave_read_only:1
connected_slaves:0
master_repl_offset:0
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0

同理配置6381端口

查看主机6379的信息

root@ubuntu:/etc/redis# redis-cli -p 6379
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2		//有两个从机
slave0:ip=127.0.0.1,port=6380,state=online,offset=281,lag=0
slave1:ip=127.0.0.1,port=6381,state=online,offset=281,lag=1
master_repl_offset:281
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:2
repl_backlog_histlen:280

注意
我们一般情况下是通过配置文件配置主从机。


主机可以写,从机只能读,主机中的所有信息都会被从机保存

# 从机不能写
127.0.0.1:6380> set name cc
(error) READONLY You can't write against a read only slave.
# 主机写后 从机可以读
root@ubuntu:/etc/redis# redis-cli -p 6379
127.0.0.1:6379> set name cc		//主机写
OK
127.0.0.1:6379> exit
root@ubuntu:/etc/redis# redis-cli -p 6380		//从机读
127.0.0.1:6380> get name
"cc"

经过测试后,得出以下结论
主机断开连接,从机依旧连接到主机的,但是没有写操作,这个时候,主机如果回来了,丛集依旧可以直接获取到主机写的信息。
如果是使用命令行配置的主从机,这个时候如果重启了。就会变回主机,只要变为从机,立马就会从主机中获取值。

复制原理

Slave启动成功连接到master后会发送一个sync同步命令,Master接收到命令,启动后台的进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后,master将传送整个数据文件到slave,并完成一次完全同步。

  • 全是复制:而slave服务在接受到数据库文件数据后,将齐存盘并加载到内存中
  • 增量复制:Master继续将新的所有收集到的修改命令以此传给slave,完成同步

但是只要是重新连接master,一次完全同步将被自动执行,我们的数据一定可以在从机看到。

哨兵模式

手动配置
在这里插入图片描述
上图种80即是79的从机,也是81的主机

127.0.0.1:6381> SLAVEOF 127.0.0.1 6380		//连接6380到6381上
OK
# 6380此时即是6379 的从机,也是6381的主机
127.0.0.1:6380> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:9
master_sync_in_progress:0
slave_repl_offset:12557
slave_priority:100
slave_read_only:1
connected_slaves:1
slave0:ip=127.0.0.1,port=6381,state=online,offset=57,lag=1
master_repl_offset:57
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:2
repl_backlog_histlen:56
# 给6379主机设置name字段,在6380 和 6381种皆可获得,这就是主从配置的传递性
127.0.0.1:6379> set name cc
OK
127.0.0.1:6379> exit
root@ubuntu:/etc/redis# redis-cli -p 6380		//6380中可以获得值
127.0.0.1:6380> get name
"cc"
127.0.0.1:6380> 
root@ubuntu:/etc/redis# redis-cli -p 6381		//6381中也可获得值
127.0.0.1:6381> get name
"cc"
127.0.0.1:6380> set age 13
(error) READONLY You can't write against a read only slave.	//6380即是主机,也是从机,只要是从机就不能写

127.0.0.1:6379> shutdown		//此时手动关闭6379  则从机自动断开与6379的联系
not connected> exit

root@ubuntu:/etc/redis# redis-server redis.conf			//此时再次开启6379 也不会恢复主从关系
127.0.0.1:6379> info replication		//此时6379中没有任何从机,需要再次手动配置主从关系
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=6380,state=online,offset=113,lag=0
master_repl_offset:113
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:2
repl_backlog_histlen:112

可以使用SLAVEOF no one让自己变成主机。其他的节点就可以手动连接到最新的这个主节点

自动配置(哨兵模式)
主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。这不是一种推荐的方式,更多时候,我们优先考虑哨兵模式。Redis从2.8开始正式提供了Sentinel(哨兵) 架构来解决这个问题。能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库。

哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。

哨兵的作用:

  • 通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。
  • 当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。
    然而一个哨兵进程对Redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了多哨兵模式。
    在这里插入图片描述

假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover[故障转移]操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。

配置哨兵

  1. 配置哨兵文件sentinel.conf文件
# sentinel monitor 被监控的名称 host port 1
sentinel monitor myredis 127.0.0.1 6379 1		# 后面的这个数字1,代表主机挂了,slave投票看让谁接替成为主机,票数最多的,就会成为主机!
  1. 启动哨兵!

在这里插入图片描述
3. 此时哨兵监视着我们的主机6379,当我们断开主机后:
在这里插入图片描述
注意
当原主机回来的时候,会变为一个从机。

哨兵模式优缺点
优点:

  • 哨兵集群,基于主从复制模式,所有主从复制的优点,它都有

  • 主从可以切换,故障可以转移,系统的可用性更好

  • 哨兵模式是主从模式的升级,手动到自动,更加健壮 缺点:
    缺点

  • Redis不好在线扩容,集群容量一旦达到上限,在线扩容就十分麻烦

  • 实现哨兵模式的配置其实是很麻烦的,里面有很多配置项

# Example sentinel.conf
哨兵sentinel实例运行的端口 默认26379

port 26379


哨兵sentinel的工作目录

dir /tmp


哨兵sentinel监控的redis主节点的 ip port

master-name  可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符".-_"组成。

quorum 当这些quorum个数sentinel哨兵认为master主节点失联 那么这时 客观上认为主节点失联了

sentinel monitor <master-name> <ip> <redis-port> <quorum>

sentinel monitor mymaster 127.0.0.1 6379 1


当在Redis实例中开启了requirepass foobared 授权密码 这样所有连接Redis实例的客户端都要提供密码

设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码

sentinel auth-pass <master-name> <password>

sentinel auth-pass mymaster MySUPER--secret-0123passw0rd


指定多少毫秒之后 主节点没有应答哨兵sentinel 此时 哨兵主观上认为主节点下线 默认30秒

sentinel down-after-milliseconds <master-name> <milliseconds>

sentinel down-after-milliseconds mymaster 30000


这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行 同步,

这个数字越小,完成failover所需的时间就越长,

但是如果这个数字越大,就意味着越 多的slave因为replication而不可用。

可以通过将这个值设为 1 来保证每次只有一个slave 处于不能处理命令请求的状态。


sentinel parallel-syncs <master-name> <numslaves>

sentinel parallel-syncs mymaster 1


故障转移的超时时间 failover-timeout 可以用在以下这些方面:

1. 同一个sentinel对同一个master两次failover之间的间隔时间。

2. 当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时。

3.当想要取消一个正在进行的failover所需要的时间。

4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了

默认三分钟

sentinel failover-timeout <master-name> <milliseconds>

sentinel failover-timeout mymaster 180000


SCRIPTS EXECUTION

配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。

对于脚本的运行结果有以下规则:

若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10

若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。

如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。

一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行。

通知型脚本:当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本,

这时这个脚本应该通过邮件,SMS等方式去通知系统管理员关于系统不正常运行的信息。调用该脚本时,将传给脚本两个参数,

一个是事件的类型,

一个是事件的描述。

如果sentinel.conf配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执行的,否则sentinel无法正常启动成功。

通知脚本

sentinel notification-script <master-name> <script-path>

sentinel notification-script mymaster /var/redis/notify.sh


客户端重新配置主节点参数脚本

当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master地址已经发生改变的信息。

以下参数将会在调用脚本时传给脚本:

<master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>

目前<state>总是“failover”,

<role>是“leader”或者“observer”中的一个。

参数 from-ip, from-port, to-ip, to-port是用来和旧的master和新的master(即旧的slave)通信的

这个脚本应该是通用的,能被多次调用,不是针对性的。

sentinel client-reconfig-script <master-name> <script-path>

sentinel client-reconfig-script mymaster /var/redis/reconfig.sh

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/168800.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • java json转map fastjson_java字符串转map

    java json转map fastjson_java字符串转mapjavajson传map,map数组

  • jsonignore注解(jsonignore根据条件生效)

    当要将list作为一个json传到前端时有可能会出现死循环。处理方法:在实体类中对不需要的属性加上@JsonIgnore

  • php视频地址隐藏,简单隐藏加密视频地址,以防止用户下载[通俗易懂]

    简单隐藏加密视频地址,以防止用户下载,采用js调用播放器:引用js文件:language=”javascript”type=”text/javascript”>//JScript文件functionselplay(ypurl){document.write(“classid=CLSID:22d6f312-b0f6-11d0-94ab-0080c74c7e95height=69id=Med…

  • DVP和MIPI接口的简单区别

    DVP和MIPI接口的简单区别区别1、DVP接口:DVP是并行传输,传输速度较慢,传输的带宽低。2、MIPI接口:MIPI是差分串行传输,速度快,抗干扰。目前分为D/C/MPHY三类。主流手机模组现在是使用MIPI_DPHY或CPHY传输:DPHY传输时使用4对差分信号传输图像数据和一对差分时钟信号。CPHY使用3组每组3根单端信号传输数据,每根单端信号能表达3个逻辑电平,相比数据传输率更高,使用引脚数更少。1、DVP接口:使用需要PCLK\sensor输出时钟、MCLK(XCLK)\外部时钟输入、VSYNC\场同步、

  • 项目的生命周期_项目生命周期的阶段

    项目的生命周期_项目生命周期的阶段项目管理的基本内容:计划、组织和监控。项目生命周期划分为3个基本的阶段:计划、实时监控和总结。根据PMBOK,项目生命周期分为5个阶段:(1)启动。>项目正式被立项,并成立项目组,宣

  • KTT条件的理解「建议收藏」

    KTT条件的理解「建议收藏」求解优化问题:对于等式约束条件,使用拉格朗日乘子法求;对于不等式约束条件,使用KTT条件求解;这两种方法求得结果只是必要条件,只有当目标函数是凸函数时,才是充分必要条件。着重讲一下KTT条件求解如下优化问题:minxf(x)s.t.gi(x)≤0(j=1,…,n)minxf(x)s.t.gi(x)≤0(j=1,…,n)min_{x}f(x)\nonumber\\…

    2022年10月25日

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号