Dubbo原始碼學習--Redis註冊中心(三)
使用 Redis 的 Key/Map 結構儲存資料結構:
- 主 Key 為服務名和型別
- Map 中的 Key 為 URL 地址
- Map 中的 Value 為過期時間,用於判斷髒資料,髒資料由監控中心刪除 3
使用 Redis 的 Publish/Subscribe 事件通知資料變更:
- 通過事件的值區分事件型別:
register
,unregister
,subscribe
,unsubscribe
- 普通消費者直接訂閱指定服務提供者的 Key,只會收到指定服務的
register
,unregister
事件 - 監控中心通過
psubscribe
功能訂閱/dubbo/*
,會收到所有服務的所有變更事件
呼叫過程:
- 服務提供方啟動時,向
Key:/dubbo/com.foo.BarService/providers
下,添加當前提供者的地址 - 並向
Channel:/dubbo/com.foo.BarService/providers
傳送register
事件 - 服務消費方啟動時,從
Channel:/dubbo/com.foo.BarService/providers
訂閱register
和unregister
事件 - 並向
Key:/dubbo/com.foo.BarService/providers
下,添加當前消費者的地址 - 服務消費方收到
register
和unregister
事件後,從Key:/dubbo/com.foo.BarService/providers
- 服務監控中心啟動時,從
Channel:/dubbo/*
訂閱register
和unregister
,以及subscribe
和unsubsribe
事件 - 服務監控中心收到
register
和unregister
事件後,從Key:/dubbo/com.foo.BarService/providers
下獲取提供者地址列表 - 服務監控中心收到
subscribe
和unsubsribe
事件後,從Key:/dubbo/com.foo.BarService/consumers
下獲取消費者地址列表
配置
<dubbo:registry address="redis://10.20.153.10:6379" />
或
<dubbo:registry address="redis://10.20.153.10:6379?backup=10.20.153.11:6379,10.20.153.12:6379" />
或
<dubbo:registry protocol="redis" address="10.20.153.10:6379" />
或
<dubbo:registry protocol="redis" address="10.20.153.10:6379,10.20.153.11:6379,10.20.153.12:6379" />
選項
- 可通過
<dubbo:registry group="dubbo" />
設定 redis 中 key 的字首,預設為dubbo
。 - 可通過
<dubbo:registry cluster="replicate" />
設定 redis 叢集策略,預設為failover
:failover
: 只寫入和讀取任意一臺,失敗時重試另一臺,需要伺服器端自行配置資料同步replicate
: 在客戶端同時寫入所有伺服器,只讀取單臺,伺服器端不需要同步,註冊中心叢集增大,效能壓力也會更大
可靠性宣告
阿里內部並沒有採用 Redis 做為註冊中心,而是使用自己實現的基於資料庫的註冊中心,即:Redis 註冊中心並沒有在阿里內部長時間執行的可靠性保障,此 Redis 橋接實現只為開源版本提供,其可靠性依賴於 Redis 本身的可靠性。
Redis註冊中心實現了RedisRegistryFactory工廠,用來生成Registry的實現類RedisRegistry。
public class RedisRegistryFactory extends AbstractRegistryFactory {
@Override
protected Registry createRegistry(URL url) {
return new RedisRegistry(url);
}
}
RedisRegistry中主要實現瞭如下介面:(1)doRegister(URL url);//向註冊中心註冊服務
(2)doUnregister(URL url);//取消服務註冊
(3)doSubscribe(URL url, NotifyListener listener);//向註冊中心監聽服務
(4)doUnsubscribe(URL url, NotifyListener listener);//取消監聽註冊中心服務
public class RedisRegistry extends FailbackRegistry {
private static final Logger logger = LoggerFactory.getLogger(RedisRegistry.class);
private static final int DEFAULT_REDIS_PORT = 6379;
private final static String DEFAULT_ROOT = "dubbo";
private final ScheduledExecutorService expireExecutor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("DubboRegistryExpireTimer", true));
private final ScheduledFuture<?> expireFuture;
private final String root;
private final Map<String, JedisPool> jedisPools = new ConcurrentHashMap<String, JedisPool>();
private final ConcurrentMap<String, Notifier> notifiers = new ConcurrentHashMap<String, Notifier>();
private final int reconnectPeriod;
private final int expirePeriod;
private volatile boolean admin = false;
private boolean replicate;
//建構函式中主要完成了redis連線相關的操作
public RedisRegistry(URL url) {
super(url);
if (url.isAnyHost()) {
throw new IllegalStateException("registry address == null");
}
GenericObjectPoolConfig config = new GenericObjectPoolConfig();
config.setTestOnBorrow(url.getParameter("test.on.borrow", true));
config.setTestOnReturn(url.getParameter("test.on.return", false));
config.setTestWhileIdle(url.getParameter("test.while.idle", false));
if (url.getParameter("max.idle", 0) > 0)
config.setMaxIdle(url.getParameter("max.idle", 0));
if (url.getParameter("min.idle", 0) > 0)
config.setMinIdle(url.getParameter("min.idle", 0));
if (url.getParameter("max.active", 0) > 0)
config.setMaxTotal(url.getParameter("max.active", 0));
if (url.getParameter("max.total", 0) > 0)
config.setMaxTotal(url.getParameter("max.total", 0));
if (url.getParameter("max.wait", url.getParameter("timeout", 0)) > 0)
config.setMaxWaitMillis(url.getParameter("max.wait", url.getParameter("timeout", 0)));
if (url.getParameter("num.tests.per.eviction.run", 0) > 0)
config.setNumTestsPerEvictionRun(url.getParameter("num.tests.per.eviction.run", 0));
if (url.getParameter("time.between.eviction.runs.millis", 0) > 0)
config.setTimeBetweenEvictionRunsMillis(url.getParameter("time.between.eviction.runs.millis", 0));
if (url.getParameter("min.evictable.idle.time.millis", 0) > 0)
config.setMinEvictableIdleTimeMillis(url.getParameter("min.evictable.idle.time.millis", 0));
String cluster = url.getParameter("cluster", "failover");
if (!"failover".equals(cluster) && !"replicate".equals(cluster)) {
throw new IllegalArgumentException("Unsupported redis cluster: " + cluster + ". The redis cluster only supported failover or replicate.");
}
replicate = "replicate".equals(cluster);
List<String> addresses = new ArrayList<String>();
addresses.add(url.getAddress());
String[] backups = url.getParameter(Constants.BACKUP_KEY, new String[0]);
if (backups != null && backups.length > 0) {
addresses.addAll(Arrays.asList(backups));
}
String password = url.getPassword();
for (String address : addresses) {
int i = address.indexOf(':');
String host;
int port;
if (i > 0) {
host = address.substring(0, i);
port = Integer.parseInt(address.substring(i + 1));
} else {
host = address;
port = DEFAULT_REDIS_PORT;
}
if (StringUtils.isEmpty(password)) {
this.jedisPools.put(address, new JedisPool(config, host, port,
url.getParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT)));
} else {
this.jedisPools.put(address, new JedisPool(config, host, port,
url.getParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT), password));
}
}
this.reconnectPeriod = url.getParameter(Constants.REGISTRY_RECONNECT_PERIOD_KEY, Constants.DEFAULT_REGISTRY_RECONNECT_PERIOD);
String group = url.getParameter(Constants.GROUP_KEY, DEFAULT_ROOT);
if (!group.startsWith(Constants.PATH_SEPARATOR)) {
group = Constants.PATH_SEPARATOR + group;
}
if (!group.endsWith(Constants.PATH_SEPARATOR)) {
group = group + Constants.PATH_SEPARATOR;
}
this.root = group;
this.expirePeriod = url.getParameter(Constants.SESSION_TIMEOUT_KEY, Constants.DEFAULT_SESSION_TIMEOUT);
this.expireFuture = expireExecutor.scheduleWithFixedDelay(new Runnable() {
public void run() {
try {
deferExpired(); // Extend the expiration time
} catch (Throwable t) { // Defensive fault tolerance
logger.error("Unexpected exception occur at defer expire time, cause: " + t.getMessage(), t);
}
}
}, expirePeriod / 2, expirePeriod / 2, TimeUnit.MILLISECONDS);
}
private void deferExpired() {
for (Map.Entry<String, JedisPool> entry : jedisPools.entrySet()) {
JedisPool jedisPool = entry.getValue();
try {
Jedis jedis = jedisPool.getResource();
try {
for (URL url : new HashSet<URL>(getRegistered())) {
if (url.getParameter(Constants.DYNAMIC_KEY, true)) {
String key = toCategoryPath(url);
if (jedis.hset(key, url.toFullString(), String.valueOf(System.currentTimeMillis() + expirePeriod)) == 1) {
jedis.publish(key, Constants.REGISTER);
}
}
}
if (admin) {
clean(jedis);
}
if (!replicate) {
break;// If the server side has synchronized data, just write a single machine
}
} finally {
jedis.close();
}
} catch (Throwable t) {
logger.warn("Failed to write provider heartbeat to redis registry. registry: " + entry.getKey() + ", cause: " + t.getMessage(), t);
}
}
}
// The monitoring center is responsible for deleting outdated dirty data
private void clean(Jedis jedis) {
Set<String> keys = jedis.keys(root + Constants.ANY_VALUE);
if (keys != null && !keys.isEmpty()) {
for (String key : keys) {
Map<String, String> values = jedis.hgetAll(key);
if (values != null && values.size() > 0) {
boolean delete = false;
long now = System.currentTimeMillis();
for (Map.Entry<String, String> entry : values.entrySet()) {
URL url = URL.valueOf(entry.getKey());
if (url.getParameter(Constants.DYNAMIC_KEY, true)) {
long expire = Long.parseLong(entry.getValue());
if (expire < now) {
jedis.hdel(key, entry.getKey());
delete = true;
if (logger.isWarnEnabled()) {
logger.warn("Delete expired key: " + key + " -> value: " + entry.getKey() + ", expire: " + new Date(expire) + ", now: " + new Date(now));
}
}
}
}
if (delete) {
jedis.publish(key, Constants.UNREGISTER);
}
}
}
}
}
public boolean isAvailable() {
for (JedisPool jedisPool : jedisPools.values()) {
try {
Jedis jedis = jedisPool.getResource();
try {
if (jedis.isConnected()) {
return true; // At least one single machine is available.
}
} finally {
jedis.close();
}
} catch (Throwable t) {
}
}
return false;
}
@Override
public void destroy() {
super.destroy();
try {
expireFuture.cancel(true);
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
try {
for (Notifier notifier : notifiers.values()) {
notifier.shutdown();
}
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
for (Map.Entry<String, JedisPool> entry : jedisPools.entrySet()) {
JedisPool jedisPool = entry.getValue();
try {
jedisPool.destroy();
} catch (Throwable t) {
logger.warn("Failed to destroy the redis registry client. registry: " + entry.getKey() + ", cause: " + t.getMessage(), t);
}
}
}
//註冊地址到redis中主 Key 為服務名和型別
//Map 中的 Key 為 URL 地址,Map 中的 Value 為過期時間,用於判斷髒資料,髒資料由監控中心刪除
@Override
public void doRegister(URL url) {
String key = toCategoryPath(url);
String value = url.toFullString();
String expire = String.valueOf(System.currentTimeMillis() + expirePeriod);
boolean success = false;
RpcException exception = null;
for (Map.Entry<String, JedisPool> entry : jedisPools.entrySet()) {
JedisPool jedisPool = entry.getValue();
try {
Jedis jedis = jedisPool.getResource();
try {
jedis.hset(key, value, expire);
jedis.publish(key, Constants.REGISTER);
success = true;
if (!replicate) {
break; // If the server side has synchronized data, just write a single machine
}
} finally {
jedis.close();
}
} catch (Throwable t) {
exception = new RpcException("Failed to register service to redis registry. registry: " + entry.getKey() + ", service: " + url + ", cause: " + t.getMessage(), t);
}
}
if (exception != null) {
if (success) {
logger.warn(exception.getMessage(), exception);
} else {
throw exception;
}
}
}
//取消註冊,刪除key值併發送通知
@Override
public void doUnregister(URL url) {
String key = toCategoryPath(url);
String value = url.toFullString();
RpcException exception = null;
boolean success = false;
for (Map.Entry<String, JedisPool> entry : jedisPools.entrySet()) {
JedisPool jedisPool = entry.getValue();
try {
Jedis jedis = jedisPool.getResource();
try {
jedis.hdel(key, value);
jedis.publish(key, Constants.UNREGISTER);
success = true;
if (!replicate) {
break; // If the server side has synchronized data, just write a single machine
}
} finally {
jedis.close();
}
} catch (Throwable t) {
exception = new RpcException("Failed to unregister service to redis registry. registry: " + entry.getKey() + ", service: " + url + ", cause: " + t.getMessage(), t);
}
}
if (exception != null) {
if (success) {
logger.warn(exception.getMessage(), exception);
} else {
throw exception;
}
}
}
//訂閱
@Override
public void doSubscribe(final URL url, final NotifyListener listener) {
String service = toServicePath(url);
Notifier notifier = notifiers.get(service);
if (notifier == null) {
//開啟執行緒來訂閱通知資料
Notifier newNotifier = new Notifier(service);
notifiers.putIfAbsent(service, newNotifier);
notifier = notifiers.get(service);
if (notifier == newNotifier) {
notifier.start();
}
}
boolean success = false;
RpcException exception = null;
for (Map.Entry<String, JedisPool> entry : jedisPools.entrySet()) {
JedisPool jedisPool = entry.getValue();
try {
Jedis jedis = jedisPool.getResource();
try {
if (service.endsWith(Constants.ANY_VALUE)) {
admin = true;
Set<String> keys = jedis.keys(service);
if (keys != null && !keys.isEmpty()) {
Map<String, Set<String>> serviceKeys = new HashMap<String, Set<String>>();
for (String key : keys) {
String serviceKey = toServicePath(key);
Set<String> sk = serviceKeys.get(serviceKey);
if (sk == null) {
sk = new HashSet<String>();
serviceKeys.put(serviceKey, sk);
}
sk.add(key);
}
for (Set<String> sk : serviceKeys.values()) {
doNotify(jedis, sk, url, Arrays.asList(listener));
}
}
} else {
doNotify(jedis, jedis.keys(service + Constants.PATH_SEPARATOR + Constants.ANY_VALUE), url, Arrays.asList(listener));
}
success = true;
break; // Just read one server's data
} finally {
jedis.close();
}
} catch (Throwable t) { // Try the next server
exception = new RpcException("Failed to subscribe service from redis registry. registry: " + entry.getKey() + ", service: " + url + ", cause: " + t.getMessage(), t);
}
}
if (exception != null) {
if (success) {
logger.warn(exception.getMessage(), exception);
} else {
throw exception;
}
}
}
@Override
public void doUnsubscribe(URL url, NotifyListener listener) {
}
private void doNotify(Jedis jedis, String key) {
for (Map.Entry<URL, Set<NotifyListener>> entry : new HashMap<URL, Set<NotifyListener>>(getSubscribed()).entrySet()) {
doNotify(jedis, Arrays.asList(key), entry.getKey(), new HashSet<NotifyListener>(entry.getValue()));
}
}
private void doNotify(Jedis jedis, Collection<String> keys, URL url, Collection<NotifyListener> listeners) {
if (keys == null || keys.isEmpty()
|| listeners == null || listeners.isEmpty()) {
return;
}
long now = System.currentTimeMillis();
List<URL> result = new ArrayList<URL>();
List<String> categories = Arrays.asList(url.getParameter(Constants.CATEGORY_KEY, new String[0]));
String consumerService = url.getServiceInterface();
for (String key : keys) {
if (!Constants.ANY_VALUE.equals(consumerService)) {
String prvoiderService = toServiceName(key);
if (!prvoiderService.equals(consumerService)) {
continue;
}
}
String category = toCategoryName(key);
if (!categories.contains(Constants.ANY_VALUE) && !categories.contains(category)) {
continue;
}
List<URL> urls = new ArrayList<URL>();
Map<String, String> values = jedis.hgetAll(key);
if (values != null && values.size() > 0) {
for (Map.Entry<String, String> entry : values.entrySet()) {
URL u = URL.valueOf(entry.getKey());
if (!u.getParameter(Constants.DYNAMIC_KEY, true)
|| Long.parseLong(entry.getValue()) >= now) {
if (UrlUtils.isMatch(url, u)) {
urls.add(u);
}
}
}
}
if (urls.isEmpty()) {
urls.add(url.setProtocol(Constants.EMPTY_PROTOCOL)
.setAddress(Constants.ANYHOST_VALUE)
.setPath(toServiceName(key))
.addParameter(Constants.CATEGORY_KEY, category));
}
result.addAll(urls);
if (logger.isInfoEnabled()) {
logger.info("redis notify: " + key + " = " + urls);
}
}
if (result == null || result.isEmpty()) {
return;
}
for (NotifyListener listener : listeners) {
notify(url, listener, result);
}
}
private String toServiceName(String categoryPath) {
String servicePath = toServicePath(categoryPath);
return servicePath.startsWith(root) ? servicePath.substring(root.length()) : servicePath;
}
private String toCategoryName(String categoryPath) {
int i = categoryPath.lastIndexOf(Constants.PATH_SEPARATOR);
return i > 0 ? categoryPath.substring(i + 1) : categoryPath;
}
private String toServicePath(String categoryPath) {
int i;
if (categoryPath.startsWith(root)) {
i = categoryPath.indexOf(Constants.PATH_SEPARATOR, root.length());
} else {
i = categoryPath.indexOf(Constants.PATH_SEPARATOR);
}
return i > 0 ? categoryPath.substring(0, i) : categoryPath;
}
private String toServicePath(URL url) {
return root + url.getServiceInterface();
}
private String toCategoryPath(URL url) {
return toServicePath(url) + Constants.PATH_SEPARATOR + url.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY);
}
//建立訊息訂閱執行緒,當服務未多個的時候建立的執行緒也是多個,比較消耗資源
private class NotifySub extends JedisPubSub {
private final JedisPool jedisPool;
public NotifySub(JedisPool jedisPool) {
this.jedisPool = jedisPool;
}
//呼叫回撥函式
@Override
public void onMessage(String key, String msg) {
if (logger.isInfoEnabled()) {
logger.info("redis event: " + key + " = " + msg);
}
if (msg.equals(Constants.REGISTER)
|| msg.equals(Constants.UNREGISTER)) {
try {
Jedis jedis = jedisPool.getResource();
try {
//更新本地記憶體中的通知資料
doNotify(jedis, key);
} finally {
jedis.close();
}
} catch (Throwable t) { // TODO Notification failure does not restore mechanism guarantee
logger.error(t.getMessage(), t);
}
}
}
@Override
public void onPMessage(String pattern, String key, String msg) {
onMessage(key, msg);
}
@Override
public void onSubscribe(String key, int num) {
}
@Override
public void onPSubscribe(String pattern, int num) {
}
@Override
public void onUnsubscribe(String key, int num) {
}
@Override
public void onPUnsubscribe(String pattern, int num) {
}
}
//接收通知執行緒
private class Notifier extends Thread {
private final String service;
private final AtomicInteger connectSkip = new AtomicInteger();
private final AtomicInteger connectSkiped = new AtomicInteger();
private final Random random = new Random();
private volatile Jedis jedis;
private volatile boolean first = true;
private volatile boolean running = true;
private volatile int connectRandom;
public Notifier(String service) {
super.setDaemon(true);
super.setName("DubboRedisSubscribe");
this.service = service;
}
private void resetSkip() {
connectSkip.set(0);
connectSkiped.set(0);
connectRandom = 0;
}
private boolean isSkip() {
int skip = connectSkip.get(); // Growth of skipping times
if (skip >= 10) { // If the number of skipping times increases by more than 10, take the random number
if (connectRandom == 0) {
connectRandom = random.nextInt(10);
}
skip = 10 + connectRandom;
}
if (connectSkiped.getAndIncrement() < skip) { // Check the number of skipping times
return true;
}
connectSkip.incrementAndGet();
connectSkiped.set(0);
connectRandom = 0;
return false;
}
//開啟執行緒監聽通知資料
@Override
public void run() {
while (running) {
try {
if (!isSkip()) {
try {
for (Map.Entry<String, JedisPool> entry : jedisPools.entrySet()) {
JedisPool jedisPool = entry.getValue();
try {
jedis = jedisPool.getResource();
try {
if (service.endsWith(Constants.ANY_VALUE)) {
if (!first) {
first = false;
Set<String> keys = jedis.keys(service);
if (keys != null && !keys.isEmpty()) {
for (String s : keys) {
doNotify(jedis, s);
}
}
resetSkip();
}
jedis.psubscribe(new NotifySub(jedisPool), service); // blocking
} else {
if (!first) {
first = false;
doNotify(jedis, service);
resetSkip();
}
jedis.psubscribe(new NotifySub(jedisPool), service + Constants.PATH_SEPARATOR + Constants.ANY_VALUE); // blocking
}
break;
} finally {
jedis.close();
}
} catch (Throwable t) { // Retry another server
logger.warn("Failed to subscribe service from redis registry. registry: " + entry.getKey() + ", cause: " + t.getMessage(), t);
// If you only have a single redis, you need to take a rest to avoid overtaking a lot of CPU resources
sleep(reconnectPeriod);
}
}
} catch (Throwable t) {
logger.error(t.getMessage(), t);
sleep(reconnectPeriod);
}
}
} catch (Throwable t) {
logger.error(t.getMessage(), t);
}
}
}
public void shutdown() {
try {
running = false;
jedis.disconnect();
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
}
}
}
相關推薦
Dubbo原始碼學習--Redis註冊中心(三)
基於 Redis 1 實現的註冊中心 2。使用 Redis 的 Key/Map 結構儲存資料結構:主 Key 為服務名和型別Map 中的 Key 為 URL 地址Map 中的 Value 為過期時間,用於判斷髒資料,髒資料由監控中心刪除 3使用 Redis 的 Publish
淘淘商城系列(二)—— 安裝zookeeper註冊中心(三)
Zookeeper的介紹 註冊中心負責服務地址的註冊與查詢,相當於目錄服務,服務提供者和消費者只在啟動時與註冊中心互動,註冊中心不轉發請求,壓力較小。使用dubbo-2.3.3以上版本,官方建議使用zookeeper作為註冊中心。 Zookeeper是Apacahe Hadoop的子專案
spring原始碼學習筆記-初始化(三)-BeanFactory
轉自http://www.sandzhang.com/blog/2011/04/05/spring-study-notes-initialization-3/ refresh()方法中在上篇obtainFreshBeanFactory()方法建立了beanfactory物
Dubbo原始碼學習--RoundRobinLoadBalance負載均衡(四)
RoundRobin LoadBalance輪循,按公約後的權重設定輪循比率。存在慢的提供者累積請求的問題,比如:第二臺機器很慢,但沒掛,當請求調到第二臺時就卡在那,久而久之,所有請求都卡在調到第二臺上。1)獲取輪詢key 服務名+方法名獲取可供呼叫的invokers個數l
Golang原始碼學習:排程邏輯(三)工作執行緒的執行流程與排程迴圈
本文內容主要分為三部分: 1. main goroutine 的排程執行 2. 非 main goroutine 的退出流程 3. 工作執行緒的執行流程與排程迴圈。 ## main goroutine 的排程執行 runtime·rt0_go中在呼叫完runtime.newproc建立main gorouti
Dubbo原始碼學習--註冊中心(一)
目前Dubbo官方提供的註冊中心有Multicast、Zookeeper、Redis和Simple註冊中心,官方推薦使用Zookeeper作為生產環境的註冊中心。 Dubbo官方也提供了擴充套件機制,開發人員可以根據自己的需要遵守一定的擴充套件規範開發自己的註冊
Redis學習筆記--Redis客戶端(三)
本機 -c trace 圖形 tro cli family 毫秒 ati 1.Redis客戶端 1.1 Redis自帶的客戶端 (1)啟動 啟動客戶端命令:[root@kwredis bin]# ./redis-cli -h 127.0.0.1 -p 6379
dubbo入門學習 四 註冊中心 zookeeper入門
一、Dubbo支援的註冊中心 1. Zookeeper 1.1 優點:支援網路叢集 1.2 缺點:穩定性受限於Zookeeper 2. Redis 2.1 優點:效能高. 2.2 缺點:對伺服器環境要求較高. 3. Multicast 3.1 優點:面中心化,不需要額外安裝
spring-cloud學習筆記Eureka註冊中心(四)修改成IP顯示
修改配置類 eureka: instance: #使用IP訪問註冊中心 prefer-ip-address: true #在註冊中心status的時候顯示的格式,這裡是 ip:埠 instance-id: ${spring.cloud.c
從零學習遊戲伺服器開發(三) CSBattleMgr服務原始碼研究
如上圖所示,這篇文章我們將介紹CSBattleMgr的情況,但是我們不會去研究這個伺服器的特別細節的東西(這些細節我們將在後面的文章中介紹)。閱讀一個未知的專案原始碼如果我們開始就糾結於各種細節,那麼我們最終會陷入“橫看成嶺側成峰,遠近高低各不同”的尷尬境界,浪費時間不說,可
結合redis設計與實現的redis原始碼學習-1-記憶體分配(zmalloc)
在進入公司後的第一個任務就是使用redis的快取功能實現伺服器的雲託管功能,在瞭解了大致需求後,依靠之前對redis的瞭解封裝了常用的redis命令,並使用單例的連線池來維護與redis的連線,使用連線池來獲取redis的連線物件,依靠這些功能基本可以實現要求的
Spring-cloud微服務實戰【三】:eureka註冊中心(中)
回憶一下,在上一篇文章中,我們建立了兩個springboot專案,並且在consumer專案中通過restTemplate進行HTTP通訊,成功訪問到了producer提供的介面,思考一下這樣的實現方式有什麼問題? 1.consumer必須知道producer的IP,才能呼叫對方的HTTP介面,並且在
spring boot 登錄註冊 demo (三) -- 前後端傳遞
lin 表單提交 www col log ref rec put 內容 前端頁面通過thymeleaf渲染 <dependency> <groupId>org.springframework.boot</gro
Oracle學習系類篇(三)
eat procedure body str 分組 錯誤 style mage 可見 1. 存儲過程 CREATE OR REPLACE PROCEDURE SP_NAME( PM_NAME [IN/OUT/IN OUT] PM_TYPE...)
小白學習之Code First(三)
數據庫 change chang chan inf 模型 code test nbsp 上下文Context類中的base構造器的幾個方法重置(1、無參 2、database name 3 、 連接字符串) 無參:如果基類base方法中無參,code first將會以 :{
機器學習算法整理(三)決策樹
outlook spa com width 選擇 clas .com img 衡量標準 決策樹的訓練與測試 如何切分特征(選擇節點) 衡量標準-熵 信息增益 決策樹構造實例 信息增益:表示特
Redis筆記整理(三):進階操作與高級部分
數據庫 NoSQL Redis [TOC] Redis筆記整理(三):進階操作與高級部分 Redis發布訂閱 Redis發布訂閱(pub/sub)是一種消息通信模式:發送者(pub)發送消息,訂閱者(sub)接收消息。 Redis客戶端可以訂閱任意數量的頻道。 下圖展示了頻道channel1,以
Redis詳解(三)
redis codis twemproxy redis集群 redis-trib.rb 一、Redis集群介紹 Clustering:redis 3.0之後進入生產環境分布式數據庫,通過分片機制來進行數據分布,clustering 內的每個節點,僅有數據庫的一部分數據;去中心化的集群:re
Python學習過程筆記整理(三)
font pytho def 駝峰 python學習 erl -s 函數參數 python 函數 -函數使用 -函數需要先定義,定義不會執行函數 -使用函數,俗稱調用 -定義函數 -格式:def 函數名稱(參數):,函數名稱不能用大駝峰,參數可以沒有 -調用函數
廖雪峰網站—學習python基礎知識(三)
style 字符串 知識 code ron sar sof 轉換 () 一、判斷 1、條件判斷 age = 18 if age >= 18: print(‘your are is‘, age) print(‘adult‘) ag