Prometheus監控有所思:多標籤埋點及Mbean

  使用 grafana+prometheus+jmx 作為普通的監控手段,是比較有用的。我之前的文章介紹了相應的實現辦法。

  但是,按照之前的實現,我們更多的只能是監控 單值型的數據,如請求量,tps 等等,對於複雜組合型的指標卻不容易監控。

  這種情況一般帶有一定的業務屬性,比如想監控mq中的每個topic的消費情況,每類產品的實時訂單情況等等。當然,對於看過完整的 prometheus 的監控數據的同學來說,會覺得很正常,因為你會看到如下的數據:

# HELP java_lang_MemoryPool_PeakUsage_max java.lang.management.MemoryUsage (java.lang<type=MemoryPool, name=Metaspace><PeakUsage>max)
# TYPE java_lang_MemoryPool_PeakUsage_max untyped
java_lang_MemoryPool_PeakUsage_max{name="Metaspace",} -1.0
java_lang_MemoryPool_PeakUsage_max{name="PS Old Gen",} 1.415053312E9
java_lang_MemoryPool_PeakUsage_max{name="PS Eden Space",} 6.96778752E8
java_lang_MemoryPool_PeakUsage_max{name="Code Cache",} 2.5165824E8
java_lang_MemoryPool_PeakUsage_max{name="Compressed Class Space",} 1.073741824E9
java_lang_MemoryPool_PeakUsage_max{name="PS Survivor Space",} 5242880.0

  這裏面的 name 就是普通標籤嘛,同理於其他埋點咯。應該是可以實現的。

  是的,prometheus 是方便實現這玩意的,但是我們之前不是使用 jmx_exportor 作為導出工具嘛,使用的埋點組件是 io.dropwizard.metrics:metrics-core 。

  而它則是重在單值的監控,所以,用它我們是實現不了帶指標的數據的監控了。

  那怎麼辦呢?三個辦法!

1. 直接替換原有的 metrics-core 組件為 prometheus 的client 組件,因為官方是支持這種操作的;
2. 使用 prometheus-client 組件與 metrics-core 組件配合,各自使用各自的功能;
3. 自行實現帶標籤的埋點,這可能是基於 MBean 的;

 

  以上這幾種方案,各有優劣。方案1可能改動太大,而且可能功能不兼容不可行; 方案2可能存在整合不了或者功能衝突情況,當然如果能整合,絕對是最好的; 方案3實現複雜度就高了,比如監控值維護、線程安全、MBean數據吐出方式等等。

  好吧,不管怎麼樣,我們還是都看看吧。

 

一、 使用 prometheus-client 埋點實現帶標籤的監控

  1. 引入 pom 依賴

        <dependency>
            <groupId>io.prometheus</groupId>
            <artifactId>simpleclient</artifactId>
            <version>0.8.0</version>
        </dependency>
        <dependency>
                <groupId>io.prometheus</groupId>
                <artifactId>simpleclient_hotspot</artifactId>
                <version>0.8.0</version>
        </dependency>
        <dependency>
                <groupId>io.prometheus</groupId>
                <artifactId>simpleclient_servlet</artifactId>
                <version>0.8.0</version>
        </dependency>

  2. 框架註冊監控

        @Configuration
        public class PrometheusConfig {
            @Bean
            public ServletRegistrationBean servletRegistrationBean(){
                // 將埋點指標吐出到 /metrics 節點
                return new ServletRegistrationBean(new MetricsServlet(), "/metrics");
            }
        }

  3. 業務埋點數據

        // 註冊指標實例
        io.prometheus.client.Counter c = io.prometheus.client.Counter.build()
                .name("jmx_test_abc_ffff")
                .labelNames("topic")
                .help("topic counter usage.")
                .register();
        public void incTopicMetric(String topic) {
            // c.labels("test").inc();  // for test
        }

  4. 獲取埋點數據信息

        curl http://localhost:8080/metrics
        # 對外暴露http接口調用,結果如下
        # HELP jmx_test_abc_ffff counter usage.
        # TYPE jmx_test_abc_ffff counter
        jmx_test_abc_ffff{topic="bbb",} 1.0
        jmx_test_abc_ffff{topic="2",} 2.0
        jmx_test_abc_ffff{topic="test",} 1.0

  可以看出,效果咱們是實現了。但是,對於已經運行的東西,要改這玩意可能不是那麼友好。主要有以下幾點:

    1. 暴露數據方式變更,原來由javaagent進行統一處理的數據,現在可能由於應用端口的不一,導致收集的配置會變更,不一定符合運維場景;
    2. 需要將原來的埋點進行替換;

 

二、 prometheus-client 與 metrics-core 混合埋點

  不處理以前的監控,將新監控帶標籤數據吐入到 jmx_exportor 中。

  我們試着使用如上的埋點方式:

        // 註冊指標實例
        io.prometheus.client.Counter c = io.prometheus.client.Counter.build()
                .name("jmx_test_abc_ffff")
                .labelNames("topic")
                .help("topic counter usage.")
                .register();
        public void incTopicMetric(String topic) {
            // c.labels("test").inc();  // for test
        }

  好像數據是不會進入的到 jmx_exportor 的。這也不奇怪,畢竟咱們也不了解其原理,難道想靠運氣取勝??

  細去查看 metrics-core 組件的埋點實現方案,發現其是向 MBean 中吐入數據,從而被 jmx_exportor 抓取的。

        // com.codahale.metrics.jmx.JmxReporter.JmxListener#onCounterAdded
        @Override
        public void onCounterAdded(String name, Counter counter) {
            try {
                if (filter.matches(name, counter)) {
                    final ObjectName objectName = createName("counters", name);
                    registerMBean(new JmxCounter(counter, objectName), objectName);
                }
            } catch (InstanceAlreadyExistsException e) {
                LOGGER.debug("Unable to register counter", e);
            } catch (JMException e) {
                LOGGER.warn("Unable to register counter", e);
            }
        }
        // 向 mBeanServer 註冊監控實例
        // 默認情況下 mBeanServer = ManagementFactory.getPlatformMBeanServer();
        private void registerMBean(Object mBean, ObjectName objectName) throws InstanceAlreadyExistsException, JMException {
            ObjectInstance objectInstance = mBeanServer.registerMBean(mBean, objectName);
            if (objectInstance != null) {
                // the websphere mbeanserver rewrites the objectname to include
                // cell, node & server info
                // make sure we capture the new objectName for unregistration
                registered.put(objectName, objectInstance.getObjectName());
            } else {
                registered.put(objectName, objectName);
            }
        }

  而 prometheus-client 則是通過 CollectorRegistry.defaultRegistry 進行註冊實例的。

    // io.prometheus.client.SimpleCollector.Builder#register()
    /**
     * Create and register the Collector with the default registry.
     */
    public C register() {
      return register(CollectorRegistry.defaultRegistry);
    }
    /**
     * Create and register the Collector with the given registry.
     */
    public C register(CollectorRegistry registry) {
      C sc = create();
      registry.register(sc);
      return sc;
    }

  所以,好像原理上來講是不同的。至於到底為什麼不能監控到數據,那還不好說。至少,你可以學習 metrics-core 使用 MBean 的形式將數據導出。這是我們下一個方案要討論的事。

  這裏我可以給到一個最終簡單又不失巧合的方式,實現兩個監控組件的兼容,同時向 jmx_exportor 進行導出。如下:

  1. 引入 javaagent 依賴包

        <!-- javaagent 包,與 外部使用的 jmx_exportor 一致 -->
        <dependency>
            <groupId>io.prometheus.jmx</groupId>
            <artifactId>jmx_prometheus_javaagent</artifactId>
            <version>0.12.0</version>
        </dependency>

  2. 使用 agent 的工具類進行埋點

  因為 javaagent 裏面提供一套完整的 client 工具包,所以,我們可以使用。

        // 註冊指標實例
        // 將 io.prometheus.client.Counter 包替換為 io.prometheus.jmx.shaded.io.prometheus.client.Counter
        io.prometheus.client.Counter c = io.prometheus.client.Counter.build()
                .name("jmx_test_abc_ffff")
                .labelNames("topic")
                .help("topic counter usage.")
                .register();
        public void incTopicMetric(String topic) {
            // c.labels("test").inc();  // for test
        }

  3. 原樣使用 jmx_exportor 就可以導出監控數據了

  為什麼換一個包這樣就可以了?

  因為 jmx_exportor 也是通過註冊 CollectorRegistry.defaultRegistry 來進行收集數據的,我們只要保持與其實例一致,就可以做到在同一個jvm內共享數據了。

 

三、 基於 MBean自行實現帶標籤的埋點

// 測試類
public class PrometheusMbeanMetricsMain {
    private static ConcurrentHashMap<String, AtomicInteger> topicContainer = new ConcurrentHashMap<>();
    private static MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();

    public static void main(String[] args) throws Exception {
        // 模擬某個topic
        String commingTopic = "test_topic";
        AtomicInteger myTopic1Counter = getMetricCounter(commingTopic);
        System.out.println("jmx started!");
        while(true){
            System.out.println("---");
            // 計數增加
            myTopic1Counter.incrementAndGet();
            Thread.sleep(10000);
        }
    }

    private static AtomicInteger getMetricCounter(String topic) throws MalformedObjectNameException, NotCompliantMBeanException, InstanceAlreadyExistsException, MBeanRegistrationException {
        AtomicInteger myTopic1Counter = topicContainer.get(topic);
        if(myTopic1Counter == null) {
            myTopic1Counter = new AtomicInteger(0);
            Hashtable<String, String> tab = new Hashtable<>();
            tab.put("topic", topic);
            // 佔位符,雖然不知道什麼意思,但是感覺很厲害的樣子
            tab.put("_", "_value");
            ObjectName objectName = new ObjectName("mydomain_test", tab);
            // 註冊監控實例 到 MBeanServer 中
            ObjectInstance objectInstance = mBeanServer.registerMBean(new JmxCounter(myTopic1Counter, objectName), objectName);
        }
        return myTopic1Counter;
    }
}
// JmxCounter, MBean 要求: 1. 接口必須定義成Public的;  2. 接口命名規範符合要求, 即接口名叫 XYZMBean ,那麼實現名就必須一定是XYZ;
// DynamicMBean
public interface JmxCounterMBean {
    public Object getCount() throws Exception;
}
public class JmxCounter implements JmxCounterMBean {
    private AtomicInteger metric;
    private ObjectName objectName;

    public JmxCounter(AtomicInteger metric, ObjectName objectName) {
        this.objectName = objectName;
        this.metric = metric;
    }

    @Override
    public Object getCount() throws Exception {
        // 返回監控結果
        return metric.get();
    }

}

  最後,見證奇迹的時刻。結果如下:

# HELP mydomain_test_value_Count Attribute exposed for management (mydomain_test<_=_value, topic=b_topic><>Count)
# TYPE mydomain_test_value_Count untyped
mydomain_test_value_Count{topic="b_topic",} 1.0
mydomain_test_value_Count{topic="a_topic",} 88.0

  很明顯,這是一個糟糕的實現,不要學他。僅為了演示效果。

  所以,總結下來,自然是使用方案2了。兩個組件兼容,實現簡單,性能也不錯。如果只是為了使用,到此就可以了。不過你得明白,以上方案有取巧的成分在。

 

四、 原理: jmx_exportor 是如何獲取數據的?

  jmx_exportor 也是可以通過 http_server 暴露數據。

    // io.prometheus.client.exporter.HTTPServer
    /**
     * Start a HTTP server serving Prometheus metrics from the given registry.
     */
    public HTTPServer(InetSocketAddress addr, CollectorRegistry registry, boolean daemon) throws IOException {
        server = HttpServer.create();
        server.bind(addr, 3);
        // 使用 HTTPMetricHandler 處理請求
        HttpHandler mHandler = new HTTPMetricHandler(registry);
        // 綁定到 /metrics 地址上
        server.createContext("/", mHandler);
        server.createContext("/metrics", mHandler);
        executorService = Executors.newFixedThreadPool(5, DaemonThreadFactory.defaultThreadFactory(daemon));
        server.setExecutor(executorService);
        start(daemon);
    }    
    /**
     * Start a HTTP server by making sure that its background thread inherit proper daemon flag.
     */
    private void start(boolean daemon) {
        if (daemon == Thread.currentThread().isDaemon()) {
            server.start();
        } else {
            FutureTask<Void> startTask = new FutureTask<Void>(new Runnable() {
                @Override
                public void run() {
                    server.start();
                }
            }, null);
            DaemonThreadFactory.defaultThreadFactory(daemon).newThread(startTask).start();
            try {
                startTask.get();
            } catch (ExecutionException e) {
                throw new RuntimeException("Unexpected exception on starting HTTPSever", e);
            } catch (InterruptedException e) {
                // This is possible only if the current tread has been interrupted,
                // but in real use cases this should not happen.
                // In any case, there is nothing to do, except to propagate interrupted flag.
                Thread.currentThread().interrupt();
            }
        }
    }

  所以,可以主要邏輯是 HTTPMetricHandler 處理。來看看。

        // io.prometheus.client.exporter.HTTPServer.HTTPMetricHandler#handle
        public void handle(HttpExchange t) throws IOException {
            String query = t.getRequestURI().getRawQuery();

            ByteArrayOutputStream response = this.response.get();
            response.reset();
            OutputStreamWriter osw = new OutputStreamWriter(response);
            // 主要由該 TextFormat 進行格式化輸出
            // registry.filteredMetricFamilySamples() 進行數據收集
            TextFormat.write004(osw,
                    registry.filteredMetricFamilySamples(parseQuery(query)));
            osw.flush();
            osw.close();
            response.flush();
            response.close();

            t.getResponseHeaders().set("Content-Type",
                    TextFormat.CONTENT_TYPE_004);
            if (shouldUseCompression(t)) {
                t.getResponseHeaders().set("Content-Encoding", "gzip");
                t.sendResponseHeaders(HttpURLConnection.HTTP_OK, 0);
                final GZIPOutputStream os = new GZIPOutputStream(t.getResponseBody());
                response.writeTo(os);
                os.close();
            } else {
                t.getResponseHeaders().set("Content-Length",
                        String.valueOf(response.size()));
                t.sendResponseHeaders(HttpURLConnection.HTTP_OK, response.size());
                // 寫向客戶端
                response.writeTo(t.getResponseBody());
            }
            t.close();
        }

    }

 

五、 原理: jmx_exportor 是如何獲取Mbean 的數據的?

  jmx_exportor 有一個 JmxScraper, 專門用於處理 MBean 的值。

    // io.prometheus.jmx.JmxScraper#doScrape
    /**
      * Get a list of mbeans on host_port and scrape their values.
      *
      * Values are passed to the receiver in a single thread.
      */
    public void doScrape() throws Exception {
        MBeanServerConnection beanConn;
        JMXConnector jmxc = null;
        // 默認直接獲取本地的 jmx 信息
        // 即是通過共享 ManagementFactory.getPlatformMBeanServer() 變量來實現通信的
        if (jmxUrl.isEmpty()) {
          beanConn = ManagementFactory.getPlatformMBeanServer();
        } else {
          Map<String, Object> environment = new HashMap<String, Object>();
          if (username != null && username.length() != 0 && password != null && password.length() != 0) {
            String[] credent = new String[] {username, password};
            environment.put(javax.management.remote.JMXConnector.CREDENTIALS, credent);
          }
          if (ssl) {
              environment.put(Context.SECURITY_PROTOCOL, "ssl");
              SslRMIClientSocketFactory clientSocketFactory = new SslRMIClientSocketFactory();
              environment.put(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE, clientSocketFactory);
              environment.put("com.sun.jndi.rmi.factory.socket", clientSocketFactory);
          }
          // 如果是遠程獲取,則會通過 rmi 進行遠程通信獲取
          jmxc = JMXConnectorFactory.connect(new JMXServiceURL(jmxUrl), environment);
          beanConn = jmxc.getMBeanServerConnection();
        }
        try {
            // Query MBean names, see #89 for reasons queryMBeans() is used instead of queryNames()
            Set<ObjectName> mBeanNames = new HashSet<ObjectName>();
            for (ObjectName name : whitelistObjectNames) {
                for (ObjectInstance instance : beanConn.queryMBeans(name, null)) {
                    mBeanNames.add(instance.getObjectName());
                }
            }

            for (ObjectName name : blacklistObjectNames) {
                for (ObjectInstance instance : beanConn.queryMBeans(name, null)) {
                    mBeanNames.remove(instance.getObjectName());
                }
            }

            // Now that we have *only* the whitelisted mBeans, remove any old ones from the cache:
            jmxMBeanPropertyCache.onlyKeepMBeans(mBeanNames);

            for (ObjectName objectName : mBeanNames) {
                long start = System.nanoTime();
                scrapeBean(beanConn, objectName);
                logger.fine("TIME: " + (System.nanoTime() - start) + " ns for " + objectName.toString());
            }
        } finally {
          if (jmxc != null) {
            jmxc.close();
          }
        }
    }
    
    // io.prometheus.jmx.JmxScraper#scrapeBean
    private void scrapeBean(MBeanServerConnection beanConn, ObjectName mbeanName) {
        MBeanInfo info;
        try {
          info = beanConn.getMBeanInfo(mbeanName);
        } catch (IOException e) {
          logScrape(mbeanName.toString(), "getMBeanInfo Fail: " + e);
          return;
        } catch (JMException e) {
          logScrape(mbeanName.toString(), "getMBeanInfo Fail: " + e);
          return;
        }
        MBeanAttributeInfo[] attrInfos = info.getAttributes();

        Map<String, MBeanAttributeInfo> name2AttrInfo = new LinkedHashMap<String, MBeanAttributeInfo>();
        for (int idx = 0; idx < attrInfos.length; ++idx) {
            MBeanAttributeInfo attr = attrInfos[idx];
            if (!attr.isReadable()) {
                logScrape(mbeanName, attr, "not readable");
                continue;
            }
            name2AttrInfo.put(attr.getName(), attr);
        }
        final AttributeList attributes;
        try {
            // 通過 MBean 調用對象,獲取所有屬性值,略去不說
            attributes = beanConn.getAttributes(mbeanName, name2AttrInfo.keySet().toArray(new String[0]));
        } catch (Exception e) {
            logScrape(mbeanName, name2AttrInfo.keySet(), "Fail: " + e);
            return;
        }
        for (Attribute attribute : attributes.asList()) {
            MBeanAttributeInfo attr = name2AttrInfo.get(attribute.getName());
            logScrape(mbeanName, attr, "process");
            // 處理單個key的屬性值, 如 topic=aaa,ip=1 將會進行再次循環處理
            processBeanValue(
                    mbeanName.getDomain(),
                    // 獲取有效的屬性列表, 我們可以簡單看一下過濾規則, 如下文
                    jmxMBeanPropertyCache.getKeyPropertyList(mbeanName),
                    new LinkedList<String>(),
                    attr.getName(),
                    attr.getType(),
                    attr.getDescription(),
                    attribute.getValue()
            );
        }
    }
    // 處理每個 mBean 的屬性,寫入到 receiver 中
    // io.prometheus.jmx.JmxScraper#processBeanValue
    /**
     * Recursive function for exporting the values of an mBean.
     * JMX is a very open technology, without any prescribed way of declaring mBeans
     * so this function tries to do a best-effort pass of getting the values/names
     * out in a way it can be processed elsewhere easily.
     */
    private void processBeanValue(
            String domain,
            LinkedHashMap<String, String> beanProperties,
            LinkedList<String> attrKeys,
            String attrName,
            String attrType,
            String attrDescription,
            Object value) {
        if (value == null) {
            logScrape(domain + beanProperties + attrName, "null");
        } 
        // 單值情況,数字型,字符串型,可以處理
        else if (value instanceof Number || value instanceof String || value instanceof Boolean) {
            logScrape(domain + beanProperties + attrName, value.toString());
            // 解析出的數據存入 receiver 中,可以是 jmx, 或者 控制台
            this.receiver.recordBean(
                    domain,
                    beanProperties,
                    attrKeys,
                    attrName,
                    attrType,
                    attrDescription,
                    value);
        } 
        // 多值型情況
        else if (value instanceof CompositeData) {
            logScrape(domain + beanProperties + attrName, "compositedata");
            CompositeData composite = (CompositeData) value;
            CompositeType type = composite.getCompositeType();
            attrKeys = new LinkedList<String>(attrKeys);
            attrKeys.add(attrName);
            for(String key : type.keySet()) {
                String typ = type.getType(key).getTypeName();
                Object valu = composite.get(key);
                processBeanValue(
                        domain,
                        beanProperties,
                        attrKeys,
                        key,
                        typ,
                        type.getDescription(),
                        valu);
            }
        } 
        // 更複雜型對象
        else if (value instanceof TabularData) {
            // I don't pretend to have a good understanding of TabularData.
            // The real world usage doesn't appear to match how they were
            // meant to be used according to the docs. I've only seen them
            // used as 'key' 'value' pairs even when 'value' is itself a
            // CompositeData of multiple values.
            logScrape(domain + beanProperties + attrName, "tabulardata");
            TabularData tds = (TabularData) value;
            TabularType tt = tds.getTabularType();

            List<String> rowKeys = tt.getIndexNames();

            CompositeType type = tt.getRowType();
            Set<String> valueKeys = new TreeSet<String>(type.keySet());
            valueKeys.removeAll(rowKeys);

            LinkedList<String> extendedAttrKeys = new LinkedList<String>(attrKeys);
            extendedAttrKeys.add(attrName);
            for (Object valu : tds.values()) {
                if (valu instanceof CompositeData) {
                    CompositeData composite = (CompositeData) valu;
                    LinkedHashMap<String, String> l2s = new LinkedHashMap<String, String>(beanProperties);
                    for (String idx : rowKeys) {
                        Object obj = composite.get(idx);
                        if (obj != null) {
                            // Nested tabulardata will repeat the 'key' label, so
                            // append a suffix to distinguish each.
                            while (l2s.containsKey(idx)) {
                              idx = idx + "_";
                            }
                            l2s.put(idx, obj.toString());
                        }
                    }
                    for(String valueIdx : valueKeys) {
                        LinkedList<String> attrNames = extendedAttrKeys;
                        String typ = type.getType(valueIdx).getTypeName();
                        String name = valueIdx;
                        if (valueIdx.toLowerCase().equals("value")) {
                            // Skip appending 'value' to the name
                            attrNames = attrKeys;
                            name = attrName;
                        } 
                        processBeanValue(
                            domain,
                            l2s,
                            attrNames,
                            name,
                            typ,
                            type.getDescription(),
                            composite.get(valueIdx));
                    }
                } else {
                    logScrape(domain, "not a correct tabulardata format");
                }
            }
        } else if (value.getClass().isArray()) {
            logScrape(domain, "arrays are unsupported");
        } else {
            // 多半會返回不支持的對象然後得不到jmx監控值
            // mydomain_test{3=3, topic=aaa} java.util.Hashtable is not exported
            logScrape(domain + beanProperties, attrType + " is not exported");
        }
    }
    
    // 我們看下prometheus 對 mbeanName 的轉換操作,會將各種特殊字符轉換為 屬性列表
    // io.prometheus.jmx.JmxMBeanPropertyCache#getKeyPropertyList
    public LinkedHashMap<String, String> getKeyPropertyList(ObjectName mbeanName) {
        LinkedHashMap<String, String> keyProperties = keyPropertiesPerBean.get(mbeanName);
        if (keyProperties == null) {
            keyProperties = new LinkedHashMap<String, String>();
            // 轉化為 string 格式
            String properties = mbeanName.getKeyPropertyListString();
            // 此處為 prometheus 認識的格式,已經匹配上了
            Matcher match = PROPERTY_PATTERN.matcher(properties);
            while (match.lookingAt()) {
                keyProperties.put(match.group(1), match.group(2));
                properties = properties.substring(match.end());
                if (properties.startsWith(",")) {
                    properties = properties.substring(1);
                }
                match.reset(properties);
            }
            keyPropertiesPerBean.put(mbeanName, keyProperties);
        }
        return keyProperties;
    }
        // io.prometheus.jmx.JmxMBeanPropertyCache#PROPERTY_PATTERN
        private static final Pattern PROPERTY_PATTERN = Pattern.compile(
            "([^,=:\\*\\?]+)" + // Name - non-empty, anything but comma, equals, colon, star, or question mark
                    "=" +  // Equals
                    "(" + // Either
                    "\"" + // Quoted
                    "(?:" + // A possibly empty sequence of
                    "[^\\\\\"]*" + // Greedily match anything but backslash or quote
                    "(?:\\\\.)?" + // Greedily see if we can match an escaped sequence
                    ")*" +
                    "\"" +
                    "|" + // Or
                    "[^,=:\"]*" + // Unquoted - can be empty, anything but comma, equals, colon, or quote
                    ")");

 

六、 原理: jmx_exportor 為什麼輸出的格式是這樣的?

  prometheus 的數據格式如下,如何從埋點數據轉換?

# HELP mydomain_test_value_Count Attribute exposed for management (mydomain_test<_=_value, topic=b_topic><>Count)
# TYPE mydomain_test_value_Count untyped
mydomain_test_value_Count{topic="b_topic",} 1.0
mydomain_test_value_Count{topic="a_topic",} 132.0

  是一個輸出格式問題,也是一協議問題。

  // io.prometheus.client.exporter.common.TextFormat#write004
  public static void write004(Writer writer, Enumeration<Collector.MetricFamilySamples> mfs) throws IOException {
    /* See http://prometheus.io/docs/instrumenting/exposition_formats/
     * for the output format specification. */
    while(mfs.hasMoreElements()) {
      Collector.MetricFamilySamples metricFamilySamples = mfs.nextElement();
      writer.write("# HELP ");
      writer.write(metricFamilySamples.name);
      writer.write(' ');
      writeEscapedHelp(writer, metricFamilySamples.help);
      writer.write('\n');

      writer.write("# TYPE ");
      writer.write(metricFamilySamples.name);
      writer.write(' ');
      writer.write(typeString(metricFamilySamples.type));
      writer.write('\n');

      for (Collector.MetricFamilySamples.Sample sample: metricFamilySamples.samples) {
        writer.write(sample.name);
        // 帶 labelNames 的,依次輸出對應的標籤
        if (sample.labelNames.size() > 0) {
          writer.write('{');
          for (int i = 0; i < sample.labelNames.size(); ++i) {
            writer.write(sample.labelNames.get(i));
            writer.write("=\"");
            writeEscapedLabelValue(writer, sample.labelValues.get(i));
            writer.write("\",");
          }
          writer.write('}');
        }
        writer.write(' ');
        writer.write(Collector.doubleToGoString(sample.value));
        if (sample.timestampMs != null){
          writer.write(' ');
          writer.write(sample.timestampMs.toString());
        }
        writer.write('\n');
      }
    }
  }

 

  done.

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

※台灣寄大陸海運貨物規則及重量限制?

※大陸寄台灣海運費用試算一覽表

※台中搬家,彰化搬家,南投搬家前需注意的眉眉角角,別等搬了再說!

花了幾個小時總結了一些容易出錯的 Java 知識點!

本文已經收錄自 (61k+Star!【Java學習+面試指南】 一份涵蓋大部分Java程序員所需要掌握的核心知識。歡迎 Star!歡迎騷擾!)
原文地址:https://javaguide.cn/2019/08/20/java/java%E5%9F%BA%E7%A1%80/Java%E7%96%91%E9%9A%BE%E7%82%B9/

1. 基礎

1.1. 正確使用 equals 方法

Object的equals方法容易拋空指針異常,應使用常量或確定有值的對象來調用 equals。

舉個例子:

// 不能使用一個值為null的引用類型變量來調用非靜態方法,否則會拋出異常
String str = null;
if (str.equals("SnailClimb")) {
  ...
} else {
  ..
}

運行上面的程序會拋出空指針異常,但是我們把第二行的條件判斷語句改為下面這樣的話,就不會拋出空指針異常,else 語句塊得到執行。:

"SnailClimb".equals(str);// false 

不過更推薦使用 java.util.Objects#equals(JDK7 引入的工具類)。

Objects.equals(null,"SnailClimb");// false

我們看一下java.util.Objects#equals的源碼就知道原因了。

public static boolean equals(Object a, Object b) {
        // 可以避免空指針異常。如果a==null的話此時a.equals(b)就不會得到執行,避免出現空指針異常。
        return (a == b) || (a != null && a.equals(b));
    }

注意:

Reference:

  • 每種原始類型都有默認值一樣,如int默認值為 0,boolean 的默認值為 false,null 是任何引用類型的默認值,不嚴格的說是所有 Object 類型的默認值。
  • 可以使用 == 或者 != 操作來比較null值,但是不能使用其他算法或者邏輯操作。在Java中null == null將返回true。
  • 不能使用一個值為null的引用類型變量來調用非靜態方法,否則會拋出異常

1.2. 整型包裝類值的比較

所有整型包裝類對象值的比較必須使用equals方法。

先看下面這個例子:

Integer x = 3;
Integer y = 3;
System.out.println(x == y);// true
Integer a = new Integer(3);
Integer b = new Integer(3);
System.out.println(a == b);//false
System.out.println(a.equals(b));//true

當使用自動裝箱方式創建一個Integer對象時,當數值在-128 ~127時,會將創建的 Integer 對象緩存起來,當下次再出現該數值時,直接從緩存中取出對應的Integer對象。所以上述代碼中,x和y引用的是相同的Integer對象。

注意:如果你的IDE(IDEA/Eclipse)上安裝了阿里巴巴的p3c插件,這個插件如果檢測到你用 ==的話會報錯提示,推薦安裝一個這個插件,很不錯。

1.3. BigDecimal

1.3.1. BigDecimal 的用處

《阿里巴巴Java開發手冊》中提到:浮點數之間的等值判斷,基本數據類型不能用==來比較,包裝數據類型不能用 equals 來判斷。 具體原理和浮點數的編碼方式有關,這裏就不多提了,我們下面直接上實例:

float a = 1.0f - 0.9f;
float b = 0.9f - 0.8f;
System.out.println(a);// 0.100000024
System.out.println(b);// 0.099999964
System.out.println(a == b);// false

具有基本數學知識的我們很清楚的知道輸出並不是我們想要的結果(精度丟失),我們如何解決這個問題呢?一種很常用的方法是:使用使用 BigDecimal 來定義浮點數的值,再進行浮點數的運算操作。

BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("0.9");
BigDecimal c = new BigDecimal("0.8");
BigDecimal x = a.subtract(b);// 0.1
BigDecimal y = b.subtract(c);// 0.1
System.out.println(x.equals(y));// true 

1.3.2. BigDecimal 的大小比較

a.compareTo(b) : 返回 -1 表示小於,0 表示 等於, 1表示 大於。

BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("0.9");
System.out.println(a.compareTo(b));// 1

1.3.3. BigDecimal 保留幾位小數

通過 setScale方法設置保留幾位小數以及保留規則。保留規則有挺多種,不需要記,IDEA會提示。

BigDecimal m = new BigDecimal("1.255433");
BigDecimal n = m.setScale(3,BigDecimal.ROUND_HALF_DOWN);
System.out.println(n);// 1.255

1.3.4. BigDecimal 的使用注意事項

注意:我們在使用BigDecimal時,為了防止精度丟失,推薦使用它的 BigDecimal(String) 構造方法來創建對象。《阿里巴巴Java開發手冊》對這部分內容也有提到如下圖所示。

1.3.5. 總結

BigDecimal 主要用來操作(大)浮點數,BigInteger 主要用來操作大整數(超過 long 類型)。

BigDecimal 的實現利用到了 BigInteger, 所不同的是 BigDecimal 加入了小數位的概念

1.4. 基本數據類型與包裝數據類型的使用標準

Reference:《阿里巴巴Java開發手冊》

  • 【強制】所有的 POJO 類屬性必須使用包裝數據類型。
  • 【強制】RPC 方法的返回值和參數必須使用包裝數據類型。
  • 【推薦】所有的局部變量使用基本數據類型。

比如我們如果自定義了一個Student類,其中有一個屬性是成績score,如果用Integer而不用int定義,一次考試,學生可能沒考,值是null,也可能考了,但考了0分,值是0,這兩個表達的狀態明顯不一樣.

說明 :POJO 類屬性沒有初值是提醒使用者在需要使用時,必須自己顯式地進行賦值,任何 NPE 問題,或者入庫檢查,都由使用者來保證。

正例 : 數據庫的查詢結果可能是 null,因為自動拆箱,用基本數據類型接收有 NPE 風險。

反例 : 比如显示成交總額漲跌情況,即正負 x%,x 為基本數據類型,調用的 RPC 服務,調用不成功時,返回的是默認值,頁面显示為 0%,這是不合理的,應該显示成中劃線。所以包裝數據類型的 null 值,能夠表示額外的信息,如:遠程調用失敗,異常退出。

2. 集合

2.1. Arrays.asList()使用指南

最近使用Arrays.asList()遇到了一些坑,然後在網上看到這篇文章: 感覺挺不錯的,但是還不是特別全面。所以,自己對於這塊小知識點進行了簡單的總結。

2.1.1. 簡介

Arrays.asList()在平時開發中還是比較常見的,我們可以使用它將一個數組轉換為一個List集合。

String[] myArray = { "Apple", "Banana", "Orange" }; 
List<String> myList = Arrays.asList(myArray);
//上面兩個語句等價於下面一條語句
List<String> myList = Arrays.asList("Apple","Banana", "Orange");

JDK 源碼對於這個方法的說明:

/**
 *返回由指定數組支持的固定大小的列表。此方法作為基於數組和基於集合的API之間的橋樑,與           Collection.toArray()結合使用。返回的List是可序列化並實現RandomAccess接口。
 */ 
public static <T> List<T> asList(T... a) {
    return new ArrayList<>(a);
}

2.1.2. 《阿里巴巴Java 開發手冊》對其的描述

Arrays.asList()將數組轉換為集合后,底層其實還是數組,《阿里巴巴Java 開發手冊》對於這個方法有如下描述:

2.1.3. 使用時的注意事項總結

傳遞的數組必須是對象數組,而不是基本類型。

Arrays.asList()是泛型方法,傳入的對象必須是對象數組。

int[] myArray = { 1, 2, 3 };
List myList = Arrays.asList(myArray);
System.out.println(myList.size());//1
System.out.println(myList.get(0));//數組地址值
System.out.println(myList.get(1));//報錯:ArrayIndexOutOfBoundsException
int [] array=(int[]) myList.get(0);
System.out.println(array[0]);//1

當傳入一個原生數據類型數組時,Arrays.asList() 的真正得到的參數就不是數組中的元素,而是數組對象本身!此時List 的唯一元素就是這個數組,這也就解釋了上面的代碼。

我們使用包裝類型數組就可以解決這個問題。

Integer[] myArray = { 1, 2, 3 };

使用集合的修改方法:add()、remove()、clear()會拋出異常。

List myList = Arrays.asList(1, 2, 3);
myList.add(4);//運行時報錯:UnsupportedOperationException
myList.remove(1);//運行時報錯:UnsupportedOperationException
myList.clear();//運行時報錯:UnsupportedOperationException

Arrays.asList() 方法返回的並不是 java.util.ArrayList ,而是 java.util.Arrays 的一個內部類,這個內部類並沒有實現集合的修改方法或者說並沒有重寫這些方法。

List myList = Arrays.asList(1, 2, 3);
System.out.println(myList.getClass());//class java.util.Arrays$ArrayList

下圖是java.util.Arrays$ArrayList的簡易源碼,我們可以看到這個類重寫的方法有哪些。

  private static class ArrayList<E> extends AbstractList<E>
        implements RandomAccess, java.io.Serializable
    {
        ...

        @Override
        public E get(int index) {
          ...
        }

        @Override
        public E set(int index, E element) {
          ...
        }

        @Override
        public int indexOf(Object o) {
          ...
        }

        @Override
        public boolean contains(Object o) {
           ...
        }

        @Override
        public void forEach(Consumer<? super E> action) {
          ...
        }

        @Override
        public void replaceAll(UnaryOperator<E> operator) {
          ...
        }

        @Override
        public void sort(Comparator<? super E> c) {
          ...
        }
    }

我們再看一下java.util.AbstractList的remove()方法,這樣我們就明白為啥會拋出UnsupportedOperationException。

public E remove(int index) {
    throw new UnsupportedOperationException();
}

2.1.4. 如何正確的將數組轉換為ArrayList?

stackoverflow:https://dwz.cn/vcBkTiTW

1. 自己動手實現(教育目的)

//JDK1.5+
static <T> List<T> arrayToList(final T[] array) {
  final List<T> l = new ArrayList<T>(array.length);

  for (final T s : array) {
    l.add(s);
  }
  return (l);
}
Integer [] myArray = { 1, 2, 3 };
System.out.println(arrayToList(myArray).getClass());//class java.util.ArrayList

2. 最簡便的方法(推薦)

List list = new ArrayList<>(Arrays.asList("a", "b", "c"))

3. 使用 Java8 的Stream(推薦)

Integer [] myArray = { 1, 2, 3 };
List myList = Arrays.stream(myArray).collect(Collectors.toList());
//基本類型也可以實現轉換(依賴boxed的裝箱操作)
int [] myArray2 = { 1, 2, 3 };
List myList = Arrays.stream(myArray2).boxed().collect(Collectors.toList());

4. 使用 Guava(推薦)

對於不可變集合,你可以使用類及其與工廠方法:(參數不能為空)

List<String> il = ImmutableList.of("string", "elements");  // from varargs
List<String> il = ImmutableList.copyOf(aStringArray);      // from array

對於可變集合,你可以使用類及其工廠方法:

List<String> l1 = Lists.newArrayList(anotherListOrCollection);    // from collection
List<String> l2 = Lists.newArrayList(aStringArray);               // from array
List<String> l3 = Lists.newArrayList("or", "string", "elements"); // from varargs

5. 使用 Apache Commons Collections

List<String> list = new ArrayList<String>();
CollectionUtils.addAll(list, str);

2.2. Collection.toArray()方法使用的坑&如何反轉數組

該方法是一個泛型方法:<T> T[] toArray(T[] a); 如果toArray方法中沒有傳遞任何參數的話返回的是Object類型數組。

String [] s= new String[]{
    "dog", "lazy", "a", "over", "jumps", "fox", "brown", "quick", "A"
};
List<String> list = Arrays.asList(s);
Collections.reverse(list);
s=list.toArray(new String[0]);//沒有指定類型的話會報錯

由於JVM優化,new String[0]作為Collection.toArray()方法的參數現在使用更好,new String[0]就是起一個模板的作用,指定了返回數組的類型,0是為了節省空間,因為它只是為了說明返回的類型。詳見:

2.3. 不要在 foreach 循環里進行元素的 remove/add 操作

如果要進行remove操作,可以調用迭代器的 remove方法而不是集合類的 remove 方法。因為如果列表在任何時間從結構上修改創建迭代器之後,以任何方式除非通過迭代器自身remove/add方法,迭代器都將拋出一個ConcurrentModificationException,這就是單線程狀態下產生的 fail-fast 機制。

fail-fast 機制 :多個線程對 fail-fast 集合進行修改的時,可能會拋出ConcurrentModificationException,單線程下也會出現這種情況,上面已經提到過。

java.util包下面的所有的集合類都是fail-fast的,而java.util.concurrent包下面的所有的類都是fail-safe的。

開源項目推薦

作者的其他開源項目推薦:

  1. :【Java學習+面試指南】 一份涵蓋大部分Java程序員所需要掌握的核心知識。
  2. : 適合新手入門以及有經驗的開發人員查閱的 Spring Boot 教程(業餘時間維護中,歡迎一起維護)。
  3. : 我覺得技術人員應該有的一些好習慣!
  4. :從零入門 !Spring Security With JWT(含權限驗證)後端部分代碼。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦、台中網頁設計公司推薦專業設計師”嚨底家”!!

※大陸寄台灣空運注意事項

※大陸海運台灣交貨時間多久?

※避免吃悶虧無故遭抬價!台中搬家公司免費估價,有契約讓您安心有保障!

SpringBoot 整合NoSql

通用配置

maven依賴

添加Spring-Web和Spring-Security依賴,使用Spring-Security是因為使用SpringBoot的Redis依賴時,必須添加Spring-Security。在新版本SpringBoot才會這樣。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

properties配置

8080端口指定一下,因為下面雙開服務器這個配置必須在這裏显示加上。

server.port=8080

測試類

@RestController
public class HelloController {

    @Value("${server.port}")
    Integer port;

    @GetMapping("/set")
    public String set(HttpSession session) {
        session.setAttribute("name", "johnson");
        return String.valueOf(port);
    }

    @GetMapping("/get")
    public String get(HttpSession session) {
        return (String)session.getAttribute("name") + port;
    }
}

整合Redis

maven依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

連接redis必須要密碼,否則連接不上,所以你的redis服務器必須設置密碼

spring.redis.host=127.0.0.1
spring.redis.database=0
spring.redis.port=6379
spring.redis.password=123456

啟動后,瀏覽器打開localhost:8080,賬號默認為user,密碼在控制台打印出來了,可以去看看。頁面如下:

Redis下的Session共享

當我們開啟兩個或多個Tomcat時,如何在這兩個Tomcat服務中共享Session呢,而Spring直接扔個依賴給你,安裝這個依賴就好了。
???????????? execute me!?
就是這麼簡單,添加spring-session-data-redis依賴就好了,如下:

<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-data-redis</artifactId>
</dependency>

測試Session共享

使用maven使用package指令打包出來出來后(IDEA的Maven工具有package按鈕,點一下就好),在target目錄下可以看到你打包好的jar包,就像這樣:

進入到tartget目錄后,打開兩個命令窗口, 分別輸入以下命令:

java -jar sessionhare-0.0.1-SNAPSHOT.jar  --server.port=8080 //窗口1命令
java -jar sessionhare-0.0.1-SNAPSHOT.jar  --server.port=8081 //窗口2命令

然後打開頁面localhost:8080,賬號默認為user,密碼可以在8080的控制台看到,登錄成功后,
再打開頁面localhost:8081,你會發現不需要再次登錄啦,Session共享成功!

Nginx的負載均衡

安裝Nginx可以參考我之前的文章
如果是Ubuntu或者其他類型的系統,就依賴項不同,安裝方式還是一樣的。

nginx配置

nginx配置在路徑在/usr/local/nginx/conf/nginx.conf, 修改配置如下:
在http模塊下修改。

    upstream colablog.cn {
        server 127.0.0.1:8080 weight=1;
        server 127.0.0.1:8081 weight=1;
    }
    server {
        listen      80;
        server_name localhost;
        
        location / {
           proxy_pass http://colablog.cn;
           proxy_redirect default;
        }
    }

修改完nginx配置後記得要重新加載一下配置文件,修改配置文件后必須重新指定配置文件,否則啟動會報錯。

sudo /usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf //重新指定配置文件
sudo /usr/local/nginx/sbin/nginx -s reload //重新啟動nginx

把剛才項目打包出來的jar包扔到Linux服務器上,讓程序在服務器後台運行,使用如下命令:

$ nohup java -jar sessionhare-0.0.1-SNAPSHOT.jar --server.port=8080 > 8080.log &
$ nohup java -jar sessionhare-0.0.1-SNAPSHOT.jar --server.port=8081 > 8081.log &

打開你Linux服務器的ip地址就可以看到了。在瀏覽器打開你的虛擬機ip/set,你的虛擬機ip/get,重複打開幾次就會發現訪問不同的端口。

MongoDb

整合MongoDb就像整合Redis那麼簡單,依賴和配置文件搞一下就行了

maven依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>

properties配置

spring.data.mongodb.host=127.0.0.1
spring.data.mongodb.authentication-database=admin
spring.data.mongodb.username=johnson
spring.data.mongodb.password=123456
spring.data.mongodb.port=27017
spring.data.mongodb.database=johnson

這樣就已經配置好了,不過我們還是測試一下吧。

測試

Book實體類

public class Book {

    private Integer id;

    private String name;

    private String author;

    @Override
    public String toString() {
        return "Book{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", author='" + author + '\'' +
                '}';
    }

    public Integer getId() { return id; }

    public void setId(Integer id) { this.id = id; }

    public String getName() { return name; }

    public void setName(String name) { this.name = name; }

    public String getAuthor() { return author; }

    public void setAuthor(String author) { this.author = author; }
}

dao接口

public interface BookDao extends MongoRepository<Book, Integer> {
    List<Book> findBookByNameContaining(String name);
}

測試類

@SpringBootTest
class MongoApplicationTests {
    @Autowired
    BookDao dao;

    @Test
    void contextLoads() {
        Book book = new Book();
        book.setName("colablog");
        book.setId(1);
        book.setAuthor("johnson");
        dao.insert(book);
    }

    @Test
    public void getList() {
        List<Book> all = dao.findAll();
        System.out.println(all);
        List<Book> cola = dao.findBookByNameContaining("cola");
        System.out.println(cola);
    }

    @Autowired
    MongoTemplate template;

    @Test
    public void test1() {
        Book book = new Book();
        book.setName("colablog2");
        book.setId(2);
        book.setAuthor("johnson2");
        template.insert(book);

        List<Book> all = template.findAll(Book.class);
        System.out.println(all);
    }
}

總結

文章主要是根據總結了視頻第六章內容,
代碼貼的有點多,因為測試用例的關係,真是抱歉,因為有測試用例才能證明程序能走通。
好了,感謝各位的閱讀,文章若有不足之處或更好的建議,請在下方留言,Thanks(・ω・)ノ。

個人博客網址: https://colablog.cn/

如果我的文章幫助到您,可以關注我的微信公眾號,第一時間分享文章給您

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※專營大陸空運台灣貨物推薦

※台灣空運大陸一條龍服務

※評比彰化搬家公司費用,南投搬家公司費用收費行情懶人包大公開

※彰化搬家費用,南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

越南市場出售活體老鼠野鳥 蒼蠅到處飛環境髒亂

摘錄自2020年03月16日聯合新聞網報導

自從武漢肺炎(COVID-19)爆發至今已三個月,而疫情的起源被指向中國湖北省武漢的華南海鮮市場,當中販賣的各式野味到處混雜,直至疫情爆發後商家才停止販售,整個市場遭封閉消毒。雖然近期野味交易在中國明令禁止,但在越南市場上仍持續進行,有民眾目擊市場內販售著活體鳥類和老鼠,現場環境髒亂,蒼蠅圍繞著野味打轉。

根據英國「太陽報」報導,越南隆安省一處市場仍在販售活體野味,遭販售的鳥類被關在籠子內,腳被綁成一束,翅膀遭折斷,且眼睛遭縫合,痛苦地在市場喊叫,目擊者表示「賣家縫住牠們的眼睛,用膠帶黏住喙,折斷翅膀,拔出羽毛,並使用一個微型儲氣瓶快速地將牠們煮熟」,而他也指出市場內還販售烏龜、蛇、老鼠和水獺等動物,現場環境相當髒亂。

生活環境
國際新聞
越南
野味
活體動物
老鼠

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步”網站設計“幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※試算大陸海運運費!

未完成恐攻應變設施 日本川內核電廠成停機首例

摘錄自2020年03月16日中央廣播電台報導

位於日本鹿兒島縣的九州電力公司川內核電廠一號機,因未能在期限內完成「恐攻應變設施」,今(16日)下午將停止反應爐運轉,寫下運轉中的核電廠因不符新規範標準停機首例。

日本放送協會(NHK)報導,日本原子力規制委員會基於新規範標準,要求核電廠在提出建設因應恐攻或航機衝撞等設施的工程計畫獲同意後,必須在五年內完成興建。九州電力公司因為無法在規定期限內完成川內核電廠一號機恐攻應變設施,預計下午停止運轉。

日本原子力規制委員會曾因為沒有規定核電廠提出因應海嘯評估與對策的期限,結果造成福島第一核電廠發生核子事故憾事。基於這樣的教訓,對因應恐攻的應變設施不僅設有期限,也不同意延長,嚴格要求核電廠不符規範就必須停機。

核能
能源議題
國際新聞
日本
核電廠
核電廠反應爐

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

※網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦、台中網頁設計公司推薦專業設計師”嚨底家”!!

※專營大陸快遞台灣服務

※台灣快遞大陸的貨運公司有哪些呢?

研究:極地冰蓋以1990年代六倍速融化 每年將有4億人受洪災影響

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計、台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

※台灣海運大陸貨務運送流程

※兩岸物流進出口一站式服務

年幼江豚大嶼山擱淺 本年第14宗

摘錄自2020年03月15日星島日報報導

香港海洋公園保育基金昨(14日)接報通知跟進在大嶼山水口一宗鯨豚擱淺個案,為本年度的第14宗。

該擱淺的年幼江豚,性別有待確定,身長133厘米。其屍身已嚴重腐爛。個案並沒有表面傷痕,保育基金已把江豚帶回海洋公園化驗。

香港海洋公園保育基金呼籲公眾,若發現懷疑鯨豚擱淺個案,可致電 1823 熱線,並提供發現擱淺的時間、地點、相片(如有),讓行動組能盡快到場跟進。

海洋
生態保育
國際新聞
香港
江豚
擱淺死亡

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包”嚨底家”

※網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

※小三通海運與一般國際貿易有何不同?

※小三通快遞通關作業有哪些?

現代轉攻新能源汽車 計畫2020年之前推26款新車

日前,據海外媒體報導,現代汽車集團將在2020年之前推出26款新能源汽車,其中包括旗下現代和起亞兩大品牌。現代汽車集團此舉意在搶佔新能源汽車市場的領先地位,爭取在2020年達到僅次於豐田的行業第二水準。

現代汽車集團計畫推出的26款新車型包括12款混動車型、6款插電式混動車型、2款純電動車型以及2款氫燃料電池車型等。其中,起亞將推出包括Niro混動版、凱尊(參配、圖片、詢價) 混動版、SoulEV在內的11款新車,而現代則會推出插電式混動版、純電動版IONIQ與豐田普銳斯一決高下。據現代汽車集團預估,到2020年現代汽車集團在新能源汽車市場的年銷量有望達到30萬台。

為了節省新能源汽車的開發成本,現代汽車集團相關負責人透露,旗下新能源車型將採用共用零部件策略。雖然每款車的動力系統在動力輸出上可能有所不同,但電動機直徑、核心部件等將會遵循統一化標準。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步”網站設計“幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※試算大陸海運運費!

2016年中國各類充電設施總投資300億元 將建私人充電樁86萬個

4月1日,國家能源局發佈《2016年能源工作指導意見》,意見明確,2016年計畫建設充電站2000多座、分散式公共充電樁10萬個,私人專用充電樁86萬個,各類充電設施總投資300億元。

意見指出,全面推進電動汽車充電設施建設。按照“樁站先行、適度超前”原則,用好財政支持政策,積極完善相關配套措施,保障工程建設順利進行。加強與建築、市政等公共設施的統籌銜接,研究編制充電設施工程技術標準規範。鼓勵大眾創業、萬眾創新,積極發展充電設施分享經濟。

另外,國家能源局制定的《電動汽車充電基礎設施建設規劃》提出,到2020年國內充換電站數量達到1.2萬個,充電樁達到450萬個。以充電樁均價2萬元/個,充電站300萬元/座計,未來六年國內新能源汽車充電樁(站)的直接市場規模有望達到1240億元。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

※網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦、台中網頁設計公司推薦專業設計師”嚨底家”!!

※專營大陸快遞台灣服務

※台灣快遞大陸的貨運公司有哪些呢?

能源局將在4-6月開展電動汽車充電基礎設施安全專項檢查

4月6日從能源局獲悉,國家能源局綜合司發佈《關於開展電動汽車充電基礎安全專項檢查的通知》,旨在加強電動汽車充電基礎設施安全管理,促進能源互聯網建設和新能源汽車產業發展。

通知指出,加強電動汽車充電基礎設施安全管理,促進能源互聯網建設和新能源汽車產業發展,定於2016年4月-6月在全國範圍內組織開展電動汽車充電基礎設施安全專項檢查。

通知明確檢查物件和內容,重點對電動汽車充電基礎設施建設運營企業以及相關充換電設施進行檢查,包括電動汽車充電基礎設施安全管理、設備設施及監控系統安全運行、建設標準執行等情況。

通知要求,全面加強電動汽車充電基礎設施安全運營管理,建立設備設施定期檢查和運行維護工作制度,確保充電設備、配電設備、線纜及保護裝置、充電監控系統及運行管理平臺的工作狀態正常和可靠運行。落實充電設備、配電等電氣設備及監控系統故障資訊檢測手段,建立充電過程的告警監測、過充保護、故障處理等防控措施及應急聯動機制。依照相關標準對有關消防設施進行檢查,保證設備處於可用狀態。加強設備設施安全管理和運行維護,滿足充換電設施運行要求。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計、台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

※台灣海運大陸貨務運送流程

※兩岸物流進出口一站式服務