Java面試題027:一文深入了解數據庫Redis(3)

Java面試題025:一文深入了解數據庫Redis(1)

Java面試題026:一文深入了解數據庫Redis(2)

????????本節我們整理一下Redis高可用和消息隊列使用場景的重點原理,讓大家在面試或者實際工作中遇到這類問題時能夠知道怎么入手,如何選擇合理的方案,至于怎么去搭建和具體的操作步驟不是我們本節的內容。

1、主從結構

????????Redis雖然讀取寫入的速度都特別快,但是也會產生讀壓力特別大的情況。為了分擔讀壓力,Redis支持主從復制,保證主數據庫的數據內容和從數據庫的內容完全一致。

????????Redis 主從架構是一種數據復制機制,用于提高數據庫的可用性和擴展性。在這種架構中,數據可以從一個主節點(master)復制到一個或多個從節點(slave)。主節點負責處理寫操作,而從節點則主要用于處理讀操作,實現讀寫分離。

? ? ? ? 根據拓撲復雜性可以分為三種:一主一從、一主多從、 樹狀主從結構。

開啟方式

master:

[root@master src]# vim /etc/redis/6379.conf
#70行 修改監聽地址為20.0.0.10 master地址
bind 20.0.0.10
#137行 開啟守護進程
daemonize yes
#172行 修改日志文件目錄
logfile /var/log/redis_6379.log
#264行 修改工作目錄
dir /var/lib/redis/6379
#700行 開啟AOF持久化功能
appendonly yes

slave1:

[root@slave1 src]# vim /etc/redis/6379.conf
#70行 修改監聽地址為20.0.0.11 slave1地址
bind 20.0.0.11
#137行 開啟守護進程
daemonize yes
#172行 修改日志文件目錄
logfile /var/log/redis_6379.log
#264行 修改工作目錄
dir /var/lib/redis/6379
#700行 開啟AOF持久化功能
appendonly yes
#287 修改IP和端口 指向master
replicaof 20.0.0.10 6379

slave2:

[root@slave1 src]# vim /etc/redis/6379.conf
#70行 修改監聽地址為20.0.0.12 slave2地址
bind 20.0.0.12
#137行 開啟守護進程
daemonize yes
#172行 修改日志文件目錄
logfile /var/log/redis_6379.log
#264行 修改工作目錄
dir /var/lib/redis/6379
#700行 開啟AOF持久化功能
appendonly yes
#287 修改IP和端口 指向master
replicaof 20.0.0.10 6379

復制原理

????????先啟動主節點,再啟動從節點,從節點啟動后,會向主數據庫發送SYNC命令。同時主數據庫收到SYNC命令后會開始在后臺保存快照(即RDB持久化,在主從復制時,會無條件觸發RDB),并將保存快照期間接收到的命令緩存起來,當快照完成后,redis會將快照文件和所有緩存命令發送給數據庫。從數據庫接收到快照文件和緩存命令后,會載入快照文件和執行命令,也就是說redis是通過RDB持久化文件和redis緩存命令來時間主從復制。----初始化復制。

????????后續每當主數據庫接到寫命令時,就會將命令同步給從數據庫,保證主從數據一致性。

? ? ? ? 主從數據庫斷線重連后,主數據庫只需要將斷線期間執行的命令傳送給從數據庫。

???????

復制方式

?????????主節點除了備份RDB文件之外還會維護者一個環形積壓隊列,以及環形隊列的寫索引和從節點同步的全局offset,環形隊列用于存儲最新的操作數據。
????????每個redis實例會擁有一個唯一的運行id,當實例重啟后,就會自動生成一個新的id。?從數據庫會存儲主數據庫的運行id。
????????主節點在復制同步階段,主數據庫每將一個命令傳遞給從數據庫時,都會將命令存放到積壓隊列,并記錄當前積壓隊列中存放命令的偏移量。

????????從數據庫接收到主數據庫傳來的命令時,會記錄下偏移量。

(1)全量復制:一般發生在Slave初始化階段

?在2.8之后,主從復制不再發送SYNC命令,取而代之的是PSYNC,格式為:“PSYNC ID offset”。

當主節點接到請求后,會判斷請求是否滿足以下兩個條件,當滿足時,不進行全量復制:

  • 從節點傳遞的run id和master的run id一致。
  • 主節點在環形隊列上可以找到對應offset的值。

1. 發送psync命令進行數據同步,由于是第一次進行復制,從節點沒有復制偏移量和主節點的運行ID,所以發送psync-1。

2. 主節點根據psync-1解析出當前為全量復制,回復+FULLRESYNC響應。

3. 從節點接收主節點的響應數據保存運行ID和偏移量offset

4. 主節點執行bgsave保存RDB文件到本地

5. 主節點發送RDB文件給從節點,從節點把接收的RDB文件保存在本地直接作為從節點數據文件

6. 對于從節點開始接收RDB快照到接收完成期間,主節點仍然響應讀寫命令,因此主節點會把這期間寫命令數據保存在復制客戶端緩沖區內,當從節點加載完RDB文件后,主節點再把緩沖區內的數據發送給從節點,保證主從之間數據一致性。

7. 從節點接收完主節點傳送來的全部數據后會清空自身舊數據

8. 從節點清空數據后開始加載RDB文件

9. 從節點成功加載完RDB后,如果當前節點開啟了AOF持久化功能, 它會立刻做bgrewriteaof操作,為了保證全量復制后AOF持久化文件立刻可用。

(2)?增量復制:

? ? ? ? 增量復制主要是Redis針對全量復制的過高開銷做出的一種優化措施, Slave初始化后開始正常工作時主服務器發生的寫操作同步到從服務器的過程。

????????增量復制的過程主要是主服務器每執行一個寫命令就會向從服務器發送相同的寫命令,從服務器接收并執行收到的寫命令。

主從結構存在問題

  • 一旦主節點出現故障,需要手動將一個從節點晉升為主節點,同時需要修改應用方的主節點地址,還需要命令其他從節點去復制新的主節點,整個過程都需要人工干預。
  • 主節點的寫能力受到單機的限制。
  • 主節點的存儲能力受到單機的限制。

2、哨兵結構

????????主從結構的手動重啟和恢復都相對麻煩,這時候就需要哨兵登場了。

????????哨兵的作用就是監控redis節點的運行狀態,監控主數據庫和從數據庫是否能夠正常運行,主數據庫出現故障時自動將從數據庫轉換為主數據庫。

當使用多個哨兵時,哨兵不僅會監控主數據庫和從數據庫,哨兵之間也會相互監控。

開啟方式

[root@master ~]# vi redis-5.0.7/sentinel.conf
17行/protected-mode no                                  #關閉保護模式
26行/daemonize yes                                      #指定sentinel為后臺啟動
36行/logfile "/var/log/sentinel.log"                    #指定日志存放路徑
65行/dir "/var/lib/redis/6379"                          #指定數據庫存放路徑
84行/sentinel monitor mymaster 20.0.0.10 6379 2         #至少幾個哨兵檢測到主服務器故障了,才會進行故障遷移,全部指向masterIP
113行/sentinel down-after-milliseconds mymaster 30000    #判定服務器down掉的時間周期,默認30000毫秒(30秒)sentinel auth-pass mymaster 123456
146行/sentinel failover-timeout mymaster 180000         #故障節的的最大超時時間為180000(180秒)

監控原理

Redis Sentinel通過三個定時監控任務完成對各個節點發現和監控:

????????1. 每隔10秒,每個Sentinel節點會向主節點和從節點發送info命令獲取最新的拓撲結構

????????2. 每隔2秒,每個Sentinel節點會向Redis數據節點的sentinelhello 頻道上發送該Sentinel節點對于主節點的判斷以及當前Sentinel節點的信息

????????3. 每隔1秒,每個Sentinel節點會向主節點、從節點、其余Sentinel節點發送一條ping命令做一次心跳檢測,來確認這些節點當前是否存活。????????

第一條操作的作用是獲取當前數據庫信息,比如發現新增從節點時,會建立連接,并加入到監控列表中,當主從數據庫的角色發生變化進行信息更新。第二條操作的作用是將自己的監控數據和哨兵分享,發送的內容為:
<哨兵地址>,<哨兵端口>,<哨兵運行id>,<哨兵配置版本>,<主數據庫名字>,<主數據庫地址>,<主數據庫端口>,<主數據庫配置版本>,每個哨兵會訂閱數據庫的_sentinel_:hello頻道,當其他哨兵收到消息后,會判斷該哨兵是不是新的哨兵,如果是則將其加入哨兵列表,并建立連接。第三條操作的作用是監控節點是否存活。該時間間隔有down-after-millisecond實現,當該值小于1s時,哨兵會按照設定的值發送ping,當大于1s時,哨兵會間隔1s發送ping命令。

主觀下線

????????主觀下線是當前哨兵節點認為某個節點有問題,客觀下線就是超過一定數量的哨兵節點認為某個主節點有問題。

????????每個Sentinel節點會每隔1秒對主節點、從節點、其他Sentinel節點發送ping命令做心跳檢測,當這些節點超過 down-after-milliseconds沒有進行有效回復,Sentinel節點就會對該節點做失敗判定,這個行為叫做主觀下線。

客觀下線

????????當Sentinel主觀下線的節點是主節點時,該Sentinel節點會通過sentinel is- master-down-by-addr命令向其他Sentinel節點詢問對主節點的判斷,當超過 <quorum>個數,Sentinel節點認為主節點確實有問題,這時該Sentinel節點會做出客觀下線的決定。

3、集群結構

????????當數據量過大到一臺服務器存放不下的情況時,主從模式或sentinel模式就不能滿足需求了,這個時候需要對存儲的數據進行分片,將數據存儲到多個Redis實例中。cluster模式的出現就是為了解決單機Redis容量有限的問題,將Redis的數據根據一定的規則分配到多臺機器。

????????使用集群,只需要將redis配置文件中的cluster-enable配置打開即可。每個集群中至少需要三個主數據庫才能正常運行。

????????所有的節點都是一主一從(也可以是一主多從),其中從不提供服務,僅作為備用。

開啟方式

參考下面這篇文章,很詳細:

redis集群搭建之官方redis cluster 搭建實踐_redis cluster搭建-CSDN博客文章瀏覽閱讀2.2w次,點贊12次,收藏73次。redis cluster是官方的redis集群實現,本篇文章為搭建集群實踐篇一、手動搭建redis官方已經redis-trib.rb命令來給我們實現redis搭建了。但是為了了解原理,首先我們來手動搭建不使用官方的命令。如果大家想快速搭建,可以直接跳到二。1、準備我們這個例子是在單機上部署集群,實際的工作情況會在不同的機器上搭建,一方面為了保證高可用也是為了擴大數據的容量所以實際中會在不同的機器..._redis cluster搭建 https://blog.csdn.net/fst438060684/article/details/80712433

集群創建過程

(1)設置節點

????????Redis集群一般由多個節點組成,節點數量至少為6個才能保證組成完整高可用的集群。每個節點需要開啟配置 cluster-enabled yes,讓Redis運行在集群模式。

(2)節點握手

????????節點握手是指一批運行在集群模式下的節點通過Gossip協議彼此通信, 達到感知對方的過程。節點握手是集群彼此通信的第一步,由客戶端發起命令:cluster meet{ip}{port}。完成節點握手之后,一個的Redis節點就組成了一個多節點的集群。

(3)分配槽(slot

????????Redis集群把所有的數據映射到16384個槽中。每個節點對應若干個槽,只有當節點分配了槽,才能響應和這些槽關聯的鍵命令。通過 cluster addslots命令為節點分配槽。

4、消息隊列

(1)使用list作為隊列

????????Redis的列表類型可以用來實現隊列,并且支持阻塞式讀取。?在Redis中,List類型是按照插入順序排序的字符串鏈表。

  • lpush生產消息,rpop消費消息

redis.properties

redis.url=localhost
redis.port=6379
redis.maxIdle=30
redis.minIdle=10
redis.maxTotal=100
redis.maxWait=10000

工具類:

public class JedisPoolUtils {private static JedisPool pool = null;static {//加載配置文件InputStream in = JedisPoolUtils.class.getClassLoader().getResourceAsStream("redis.properties");Properties pro = new Properties();try {pro.load(in);} catch (IOException e) {e.printStackTrace();}//獲得池子對象JedisPoolConfig poolConfig = new JedisPoolConfig();poolConfig.setMaxIdle(Integer.parseInt(pro.get("redis.maxIdle").toString()));//最大閑置個數poolConfig.setMaxWaitMillis(Integer.parseInt(pro.get("redis.maxWait").toString()));//最大閑置個數poolConfig.setMinIdle(Integer.parseInt(pro.get("redis.minIdle").toString()));//最小閑置個數poolConfig.setMaxTotal(Integer.parseInt(pro.get("redis.maxTotal").toString()));//最大連接數pool = new JedisPool(poolConfig, pro.getProperty("redis.url"), Integer.parseInt(pro.get("redis.port").toString()));}//獲得jedis資源的方法public static Jedis getJedis() {return pool.getResource();}
}

消息生產者:

public class MessageProducer extends Thread {public static final String MESSAGE_KEY = "message:queue";private volatile int count;public void putMessage(String message) {Jedis jedis = JedisPoolUtils.getJedis();Long size = jedis.lpush(MESSAGE_KEY, message);System.out.println(Thread.currentThread().getName() + " put message,size=" + size + ",count=" + count);count++;}@Overridepublic synchronized void run() {for (int i = 0; i < 5; i++) {putMessage("message" + count);}}public static void main(String[] args) {MessageProducer messageProducer = new MessageProducer();Thread t1 = new Thread(messageProducer, "thread1");Thread t2 = new Thread(messageProducer, "thread2");Thread t3 = new Thread(messageProducer, "thread3");Thread t4 = new Thread(messageProducer, "thread4");Thread t5 = new Thread(messageProducer, "thread5");t1.start();t2.start();t3.start();t4.start();t5.start();}
}

redis后臺查看:

127.0.0.1:6379> lrange message:queue 0 -11) "message24"2) "message23"3) "message22"4) "message21"5) "message20"6) "message19"7) "message18"8) "message17"9) "message16"
10) "message15"
11) "message14"
12) "message13"
13) "message12"
14) "message11"
15) "message10"
16) "message9"
17) "message8"
18) "message7"
19) "message6"
20) "message5"
21) "message4"
22) "message3"
23) "message2"
24) "message1"
25) "message0"

消費者:

public class MessageConsumer implements Runnable {public static final String MESSAGE_KEY = "message:queue";private volatile int count;public void consumerMessage() {Jedis jedis = JedisPoolUtils.getJedis();String message = jedis.rpop(MESSAGE_KEY);System.out.println(Thread.currentThread().getName() + " consumer message,message=" + message + ",count=" + count);count++;}@Overridepublic void run() {while (true) {consumerMessage();}}public static void main(String[] args) {MessageConsumer messageConsumer = new MessageConsumer();Thread t1 = new Thread(messageConsumer, "thread6");Thread t2 = new Thread(messageConsumer, "thread7");t1.start();t2.start();}
}

結果:

thread6 consumer message,message=message0,count=0
thread6 consumer message,message=message1,count=1
thread6 consumer message,message=message2,count=2
thread6 consumer message,message=message3,count=3
thread7 consumer message,message=message4,count=4
thread6 consumer message,message=message5,count=5
thread7 consumer message,message=message6,count=6
thread6 consumer message,message=message7,count=7
thread7 consumer message,message=message8,count=8
thread6 consumer message,message=message9,count=9
thread7 consumer message,message=message10,count=10
thread6 consumer message,message=message11,count=11
thread7 consumer message,message=message12,count=12
thread6 consumer message,message=message13,count=13
thread7 consumer message,message=message14,count=14
thread6 consumer message,message=message15,count=15
thread7 consumer message,message=message16,count=16
thread6 consumer message,message=message17,count=16
thread7 consumer message,message=message18,count=18
thread6 consumer message,message=message19,count=19
thread7 consumer message,message=message20,count=20
thread6 consumer message,message=message21,count=20
thread7 consumer message,message=message22,count=22
thread6 consumer message,message=message23,count=22
thread7 consumer message,message=message24,count=24
thread6 consumer message,message=null,count=25
thread7 consumer message,message=null,count=26
thread6 consumer message,message=null,count=27
thread7 consumer message,message=null,count=28
thread6 consumer message,message=null,count=28
thread7 consumer message,message=null,count=30
thread6 consumer message,message=null,count=31...

????????這種方式,消費者死循環rpop從隊列中消費消息。即使隊列里沒有消息,也會進行rpop,會導致Redis CPU的消耗。

  • lpush生產消息,brpop消費消息
????????brpop是 rpop 的阻塞版本, list 為空的時候,它會一直阻塞,直到 list 中有值或者超時。
public class MessageConsumer implements Runnable {public static final String MESSAGE_KEY = "message:queue";private volatile int count;private Jedis jedis = JedisPoolUtils.getJedis();public void consumerMessage() {List<String> brpop = jedis.brpop(0, MESSAGE_KEY);//0是timeout,返回的是一個集合,第一個是消息的key,第二個是消息的內容System.out.println(brpop);}@Overridepublic void run() {while (true) {consumerMessage();}}public static void main(String[] args) {MessageConsumer messageConsumer = new MessageConsumer();Thread t1 = new Thread(messageConsumer, "thread6");Thread t2 = new Thread(messageConsumer, "thread7");t1.start();t2.start();}
}

(2)使pub/sub來進行消息的發布/訂閱

????????redis還提供了一組命令可以讓開發者實現"發布/訂閱"(publish/subscribe)模式。"發布/訂閱"模式包含兩種角色,分別是發布者和訂閱者。訂閱者可以訂閱一個或者多個頻道(channel),而發布者可以向指定的頻道(channel)發送消息,所有訂閱此頻道的訂閱者都會收到此消息。

????????發布者發布消息的命令是? publish,用法是 publish channel message。

????????訂閱頻道的命令是 subscribe,可以同時訂閱多個頻道,用法是 subscribe channel1 [channel2 ...]。不會收到訂閱之前就發布到該頻道的消息。

????????還可以使用psubscribe命令訂閱指定的規則。規則支持通配符格式。命令格式為? ? ? psubscribe pattern [pattern ...]訂閱多個模式的頻道。

  通配符中?表示1個占位符,*表示任意個占位符(包括0),?*表示1個以上占位符。

例如訂閱者訂閱三個通配符頻道: psubscribe c? b* d?*

C:\Users\liqiang>redis-cli
127.0.0.1:6379> publish c m1
(integer) 0
127.0.0.1:6379> publish c1 m1
(integer) 1
127.0.0.1:6379> publish c11 m1
(integer) 0
127.0.0.1:6379> publish b m1
(integer) 1
127.0.0.1:6379> publish b1 m1
(integer) 1
127.0.0.1:6379> publish b11 m1
(integer) 1
127.0.0.1:6379> publish d m1
(integer) 0
127.0.0.1:6379> publish d1 m1
(integer) 1
127.0.0.1:6379> publish d11 m1
(integer) 1

????????上面返回值為1表示被訂閱者所接受,可以匹配上面的通配符。????????

????????使用psubscribe命令可以重復訂閱同一個頻道,如客戶端執行了psubscribe c? c?*。這時向c1發布消息客戶端會接受到兩條消息

生產者:

public class MessageProducer extends Thread {public static final String CHANNEL_KEY = "channel:1";private volatile int count;public void putMessage(String message) {Jedis jedis = JedisPoolUtils.getJedis();Long publish = jedis.publish(CHANNEL_KEY, message);//返回訂閱者數量System.out.println(Thread.currentThread().getName() + " put message,count=" + count+",subscriberNum="+publish);count++;}@Overridepublic synchronized void run() {for (int i = 0; i < 5; i++) {putMessage("message" + count);}}public static void main(String[] args) {MessageProducer messageProducer = new MessageProducer();Thread t1 = new Thread(messageProducer, "thread1");Thread t2 = new Thread(messageProducer, "thread2");Thread t3 = new Thread(messageProducer, "thread3");Thread t4 = new Thread(messageProducer, "thread4");Thread t5 = new Thread(messageProducer, "thread5");t1.start();t2.start();t3.start();t4.start();t5.start();}
}

subscribe消費者:

public class MessageConsumer implements Runnable {public static final String CHANNEL_KEY = "channel:1";//頻道public static final String EXIT_COMMAND = "exit";//結束程序的消息private MyJedisPubSub myJedisPubSub = new MyJedisPubSub();//處理接收消息public void consumerMessage() {Jedis jedis = JedisPoolUtils.getJedis();jedis.subscribe(myJedisPubSub, CHANNEL_KEY);//第一個參數是處理接收消息,第二個參數是訂閱的消息頻道}@Overridepublic void run() {while (true) {consumerMessage();}}public static void main(String[] args) {MessageConsumer messageConsumer = new MessageConsumer();Thread t1 = new Thread(messageConsumer, "thread5");Thread t2 = new Thread(messageConsumer, "thread6");t1.start();t2.start();}
}/*** 繼承JedisPubSub,重寫接收消息的方法*/
class MyJedisPubSub extends JedisPubSub {@Override/** JedisPubSub類是一個沒有抽象方法的抽象類,里面方法都是一些空實現* 所以可以選擇需要的方法覆蓋,這兒使用的是SUBSCRIBE指令,所以覆蓋了onMessage* 如果使用PSUBSCRIBE指令,則覆蓋onPMessage方法* 當然也可以選擇BinaryJedisPubSub,同樣是抽象類,但方法參數為byte[]**/public void onMessage(String channel, String message) {System.out.println(Thread.currentThread().getName()+"-接收到消息:channel=" + channel + ",message=" + message);//接收到exit消息后退出if (MessageConsumer.EXIT_COMMAND.equals(message)) {System.exit(0);}}
}

psubscribe消費者:

public class MessageConsumer implements Runnable {public static final String CHANNEL_KEY = "channel*";//頻道public static final String EXIT_COMMAND = "exit";//結束程序的消息private MyJedisPubSub myJedisPubSub = new MyJedisPubSub();//處理接收消息public void consumerMessage() {Jedis jedis = JedisPoolUtils.getJedis();jedis.psubscribe(myJedisPubSub, CHANNEL_KEY);//第一個參數是處理接收消息,第二個參數是訂閱的消息頻道}@Overridepublic void run() {while (true) {consumerMessage();}}public static void main(String[] args) {MessageConsumer messageConsumer = new MessageConsumer();Thread t1 = new Thread(messageConsumer, "thread5");Thread t2 = new Thread(messageConsumer, "thread6");t1.start();t2.start();}
}/*** 繼承JedisPubSub,重寫接收消息的方法*/
class MyJedisPubSub extends JedisPubSub {@Overridepublic void onPMessage(String pattern, String channel, String message) {System.out.println(Thread.currentThread().getName()+"-接收到消息:pattern="+pattern+",channel=" + channel + ",message=" + message);//接收到exit消息后退出if (MessageConsumer.EXIT_COMMAND.equals(message)) {System.exit(0);}}
}

(3)缺點

Redis可以提供基本的發布訂閱功能,但畢竟不像消息隊列那種專業級別,所以會存在以下缺點:

  • redis無法對消息持久化存儲,消息一旦被發送,如果沒有訂閱者接收,數據會丟失

  • 消息隊列提供了消息傳輸保障,當客戶端連接超時或事物回滾的等情況發生時,消息會重新發布給訂閱者,redis沒有該保障,導致的結果就是在訂閱者斷線超時或其他異常情況時,將會丟失所有發布者發布的信息

  • 若訂閱者訂閱了頻道,但自己讀取消息的速度很慢的話,那么不斷積壓的消息會使redis輸出緩沖區的體積變得越來越大,這可能使得redis本身的速度變慢,甚至直接崩潰

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/86333.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/86333.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/86333.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

算法打卡 day4

4 . 高精度算法 性質&#xff1a;數組或者容器從低位往高位依次存儲大整數&#xff0c;方便進位。 4.1 高精度加法 給定兩個正整數&#xff08;不含前導 0&#xff09;&#xff0c;計算它們的和。 輸入格式 共兩行&#xff0c;每行包含一個整數。 輸出格式 共一行&#xff0c;…

【筆記】Docker 配置阿里云鏡像加速(公共地址即開即用,無需手動創建實例)

2025年06月25日記 【好用但慎用】Windows 系統中將所有 WSL 發行版從 C 盤遷移到 非系統 盤的完整筆記&#xff08;附 異常處理&#xff09;-CSDN博客 【筆記】解決 WSL 遷移后 Docker 出現 “starting services: initializing Docker API Proxy: setting up docker ap” 問題…

day35-Django(1)

day35-Django 3.2 前言 之前我們介紹過web應用程序和http協議,簡單了解過web開發的概念。Web應用程序的本質 接收并解析HTTP請求,獲取具體的請求信息處理本次HTTP請求,即完成本次請求的業務邏輯處理構造并返回處理結果——HTTP響應import socketserver = socket.socket() …

PostgreSQL全棧部署指南:從零構建企業級高可用數據庫集群

PostgreSQL全棧部署指南:從零構建企業級數據庫集群 前言: 本文詳解了**PostgreSQL**所有的部署方式,如 yum 安裝、源碼編譯安裝、RPM包手動安裝,以及如何選擇適合的安裝方式。適合不同的場景應用。通過高可用部署詳細了解安裝思路及過程,包括內網環境下的配置、主節點的創…

MQTT 和 HTTP 有什么本質區別?

MQTT 和 HTTP 的本質區別在于它們設計的初衷和核心工作模式完全不同。它們是為解決不同問題而創造的兩種工具。 簡單來說&#xff1a; HTTP 就像是去圖書館問問題&#xff1a;你&#xff08;客戶端&#xff09;主動去找圖書管理員&#xff08;服務器&#xff09;&#xff0c;…

GtkSharp跨平臺WinForm實現

文章目錄 跨平臺架構設計跨平臺項目配置GtkSharp串口通訊實現跨平臺部署配置Linux系統配置macOS系統配置 相關學習資源GTK#跨平臺開發跨平臺.NET開發Linux開發環境macOS開發環境跨平臺UI框架對比容器化部署開源項目參考性能優化與調試 跨平臺架構設計 基于GTKSystem.Windows.F…

【閑談】對于c++未來的看法

對于C未來看法 C 作為一門誕生于上世紀的編程語言&#xff0c;在軟件工業發展史上扮演了不可替代的角色。盡管近年來諸如 Rust、Go、Swift、Kotlin 等現代語言相繼崛起&#xff0c;C 依然在系統軟件、高性能服務、嵌入式等關鍵領域中發揮著主力作用。本文將從 C 的當前應用前景…

【論文】云原生事件驅動架構在智能風控系統中的實踐與思考

摘要 2023年6月至2024年3月,我作為某頭部證券公司新一代極速交易系統的首席架構師,主導設計并落地了基于云原生事件驅動架構的全新交易風控平臺。該項目旨在攻克原有系統無法支撐峰值20萬筆/秒交易量、風控延遲超過3秒以及行情劇烈波動時系統崩潰等核心痛點。通過構建以Kube…

opensbi從0到1入門學習

最近要在RV64的平臺上把Linux給bringup起來&#xff0c;由于當下的工作主要集中在底層硬件接口驅動、CPU的操作及RTOS應用等&#xff0c;雖然之前搞過Arm Linux的開發工作&#xff0c;但是比較基礎的玩的比較少&#xff0c;所以真正要搞把系統bringup起來&#xff0c;我之前的知…

Python打卡:Day36

復習日 浙大疏錦行

開發過程中的時空權衡:如何優雅地平衡時間與空間效率

文章目錄 恒的開發者困境一、理解時間與空間的基本概念1. 時間復雜度2. 空間復雜度 二、時空權衡的基本原則1. 硬件環境決定優先級2. 應用場景決定策略3. 數據規模的影響 三、實際開發中的權衡策略1. 緩存為王&#xff1a;用空間換時間2. 壓縮數據&#xff1a;用時間換空間3. 預…

RAG 應用實戰指南:從商業目標到系統落地與運營 E2E 實踐

專欄入口 前言 在當今信息爆炸的時代&#xff0c;如何高效地從海量數據中提取有用信息并提供智能問答服務&#xff0c;成為眾多企業關注的焦點。檢索增強生成&#xff08;Retrieval-Augmented Generation, RAG&#xff09;技術以其結合了檢索模型的精準性和生成模型的靈活性&a…

關于晨脈的概念解釋

晨脈&#xff08;Resting Morning Pulse&#xff09;是指??人體在清晨清醒后、未進行任何活動前??&#xff0c;于臥床狀態下測量的每分鐘脈搏或心率次數。它反映了人體在無運動消耗、無神經干擾時的基礎代謝狀態&#xff0c;是評估心臟功能、身體恢復情況及運動適應性的重要…

自然語言處理入門

一、概念 自然語言處理&#xff08;Natural Language Processing, 簡稱NLP&#xff09;是計算機科學與語言中關注于計算機與人類語言間轉換的領域。 二、發展史 2012年&#xff1a;深度學習的崛起 Word2Vec的提出&#xff08;Mikolov等&#xff0c;2013年正式發表&#xff0c…

【算法 day12】LeetCode 226.翻轉二叉樹 |101. 對稱二叉樹 |104.二叉樹的最大深度|111.二叉樹的最小深度

226.翻轉二叉樹 &#xff08;前序&#xff0c;后序&#xff09; 題目鏈接 | 文檔講解 |視頻講解 : 鏈接 1.思路&#xff1a; 翻轉的是指針&#xff0c;不是數值 前序遍歷和后序遍歷都可以 中序不行&#xff0c;中序遍歷的順序是左中右,反轉左指針后,到根節點&#xff0c;…

Spring Boot 整合 Swagger3 如何生成接口文檔?

前后端分離的項目&#xff0c;接口文檔的存在十分重要。與手動編寫接口文檔不同&#xff0c;swagger是一個自動生成接口文檔的工具&#xff0c;在需求不斷變更的環境下&#xff0c;手動編寫文檔的效率實在太低。與新版的swagger3相比swagger2配置更少&#xff0c;使用更加方便。…

Rust 的智能指針

在 Rust 中&#xff0c;智能指針是一種特殊的數據結構&#xff0c;它不僅存儲數據的地址&#xff0c;還提供了額外的功能&#xff0c;如自動內存管理、引用計數等。智能指針在 Rust 中非常重要&#xff0c;因為它們幫助開發者管理內存&#xff0c;同時保持代碼的安全性和效率。…

Redis RDB 持久化:原理、觸發方式與優缺點全解析

引言 作為 Redis 最經典的持久化機制之一&#xff0c;RDB&#xff08;Redis DataBase&#xff09;憑借高效的快照生成能力和快速的恢復速度&#xff0c;一直是開發者的心頭好。但很多人對它的底層原理、觸發時機和適用場景仍存在疑惑。今天咱們就對RDB進行全解析&#xff0c;幫…

設計模式精講 Day 12:代理模式(Proxy Pattern)

【設計模式精講 Day 12】代理模式&#xff08;Proxy Pattern&#xff09; 文章內容 在軟件開發中&#xff0c;代理模式是一種常見的結構型設計模式&#xff0c;它通過引入一個代理對象來控制對真實對象的訪問。這種模式不僅能夠增強系統的安全性、靈活性和可擴展性&#xff0c…

企業級知識庫私有化部署:騰訊混元+云容器服務TKE實戰

1. 背景需求分析 在金融、醫療等數據敏感行業&#xff0c;企業需要構建完全自主可控的知識庫系統。本文以某證券機構智能投研系統為原型&#xff0c;演示如何基于騰訊混元大模型與TKE容器服務實現&#xff1a; 千億級參數模型的私有化部署金融領域垂直場景微調高并發低延遲推…