熊出沒?台南逾20棵香蕉被攔腰折斷 農業局研判:大型狗所為

摘錄自2019年12月16日自由時報報導

台南市日前傳出疑似有熊出沒,安南區公塭里一處香蕉園,一連3天遭到不明大型動物破壞,超過棵20棵香蕉被攔腰折斷,現場並留有不明爪痕、動物毛髮和手掌大的腳印,居民議論紛紛,懷疑是有熊出沒。台南市農業局經派人拍照、採證後,並送屏科大及台中自然科博館鑑定。農業局森保科長楊錦樹表示,經屏科大判定並非熊所為,而是大型狗所造成的,先前台南左鎮區也有香蕉園出現類似情形,屏科大也鑑定是大型狗造成的。而現場因有兩種不同大小的動物腳印,可能也有兩種動物到過現場。

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

【其他文章推薦】

噴霧洗滌塔實際應用案例分享

※各種攤販小吃設備,沙威瑪機自動旋轉烤玉米機,側火烤爐應有盡有!

※想知道CNC 自動車床與CNC車床有何區別??

示波器鮮為人知的使用技巧?

※客製專屬滑鼠墊、可愛造型L夾L型資料夾、透明證件套、手提袋,專業印刷設計廠商!  

※使用真空封口機常見問題?

設備減油煙 斗六成功夜市裝設率達百分百 成雲林首座環保示範夜市

摘錄自2019年12月16日自由時報報導

改善夜市油煙味、空氣品質,雲林縣環保局輔導攤販設置油煙處理設備,其中,斗六市成功夜市裝設率達百分百,空氣品質變好,成為雲林縣首座環保示範夜市。

雲林環保局空氣噪音管理科長廖崇圜指出,夜市衍生的環境問題是油煙、污水、噪音,其中油煙污染物以PM2.5為主,時間短但濃度高,加上人潮擁擠擴散不易,令人詬病。廖崇圜說,今年環保局挑選斗六市成功夜市辦理專區輔導改善,全部43家業者,其中兩家早已裝設,41家接受輔導裝設,裝設率達到百分百,環保局皆發給一張「輕油煙」標章。

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

【其他文章推薦】

飲水機設備有哪些?

※如何選購橡膠製品橡膠按鍵規格有哪些?該如何應用在商品上?  

AVX DistributorAVX TPSAVX鉭質電容器 規格有哪些?各別作用在於?

nbr乳膠手套可適用在什麼環境?

※空壓機何時可換油? 空壓機保養的正確觀念與維護 !

真空封口機該不該買?使用心得分享

中國牟利炒豬團作梗 非洲豬瘟防疫難

摘錄自2019年12月13日中央社報導

非洲豬瘟在中國爆發逾年,疫情仍持續傳出。根據一些大陸媒體表示,疫情難以絕禁的原因之一,在於「炒豬團」為了「壓價收豬」而作出各種手段,以求謀取暴利,如散播疫情謠言,低價收購生豬,是「炒豬團」的常用手段,或在一些地方,炒豬團先向養殖場、養殖戶的豬群邊丟棄死豬,或是投放帶非洲豬瘟病毒的餌料,然後製造、散布發生疫情的消息,大幅壓低價格買豬。

此外,有諸多其他因素,如動物經紀人非法調運生豬、官方獸醫違規出證以便將生豬銷售到他市、公職人員監管不力亦是造成疫情的主要原因。

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

【其他文章推薦】

※想知道台中食品倉有哪些?  

堆高機基本駕駛指導教學(影片)

廢氣洗滌塔,叫得動, 找得到的專業廠商‎

營業用紅外線烤爐比價站,怕你買貴,幫你找好康~

※市面十大品牌封口機!該如何選購?

塑膠射出成型加工商品有哪些?

塑膠廢料起火燃燒 高雄路竹工廠接續大火 3家業者移送檢調

摘錄自2019年12月15日自由時報報導

高雄路竹區大仁路工廠15凌晨2時40分驚傳火警,直到4時許才控制火勢,6時許才撲滅火勢,所幸無人傷亡,起火原因由消防局調查。環保局據報到場稽查,發現燃燒2家工廠,其中1家廠內燃燒肥料,燃燒面積100平方公尺,另1家廠內燃燒塑膠粒,燃燒面積10平方公尺,周邊可見明顯粒狀污染物逸散,未有效處理致造成污染空氣,將依空污法開罰10萬起跳,最重可罰500萬元。

由於這一帶多家工廠,不到1個月發生2起火警,造成3家工廠受損,環保局稽查發現造成空氣污染,且均是塑膠廢料起火燃燒,同是1塊土地分租給多家工廠,由於塑膠廢料難處理,懷疑案情不單純,將3家業者移送檢調。

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

【其他文章推薦】

※使用真空封口機常見問題?

※高價位跟低價位的示波器又有何差異?

※各大百貨每波促銷贈品活動,限量知名LOGOL型資料夾,獨家販售中!!

※哪裡有合版印刷優惠,尋找L夾客製化印刷廠商?

※如何正確使用開飲機?

洗滌塔運作原理介紹

不滿金門縣議會新大樓與民爭綠地?逾千人野餐抗議

摘錄自2019年12月15日自由時報報導

金門縣議會去年斥資9580萬元興建「生活館」還未驗收,今年又發文要求縣府撥用2.5公頃莒光公園用地,甚至準備編列近10億元預算興建「議政大樓」,縣府基於「尊重」議會的立場已完成撥用,消息一出引來民眾反彈。15日有上千人到現場「野餐」,用行動告訴府、會心裡的不爽快,並盼盡速撤回大樓興建案,還給民眾一個城市綠廊與休閒生活空間。

這場名為「公民好朋友的冬日野餐」活動,由縣議員董森堡服務團隊、金門戰地史蹟學會、浯江守護聯盟、金門部落主辦,地點在莒光公園;主辦單位擔心曲高和寡,一度預估只有200人出席,未料,在冬陽的暖光催動下,現場擠進逾千人。

董森堡疾呼,這片綠地是屬於大家的空間,絕不是屬於縣府或議會的;藉此活動要讓大家體認到人和環境的重要,議會將編近十億元蓋大樓,但小朋友童年美好的回憶很重要,是再多錢都買不回來的。

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

【其他文章推薦】

※一般小吃店常見使用的nbr耐油手套,是否有含耐酸鹼作用?

迴轉式空壓機性能介紹 !

海線倉儲類人員薪水待遇最新情報

※(全省)堆高機租賃保養一覽表

※高效率洗滌塔活性碳設備,能去除多少有機溶劑?

射出成型技師工作甘苦談

java property 配置文件管理工具框架,避免寫入 property 亂序

property

是 java 實現的 property 框架。

特點

  • 優雅地進行屬性文件的讀取和更新

  • 寫入屬性文件后屬性不亂序

  • 靈活定義編碼信息

  • 使用 OO 的方式操作 property 文件

  • 支持多級對象引用

變更日誌

快速開始

環境依賴

Maven 3.x

Jdk 1.7+

Maven 引入依賴

<dependency>
    <groupId>com.github.houbb</groupId>
    <artifactId>property</artifactId>
    <version>0.0.4</version>
</dependency>

入門案例

讀取屬性

PropertyBs.getInstance("read.properties").get("hello");

read.properties 為文件路徑,hello 為存在的屬性值名稱。

讀取屬性指定默認值

final String value = PropertyBs.getInstance("read.properties")
                .getOrDefault("hello2", "default");

read.properties 為文件路徑,hello2 為不存在的屬性值名稱,default 為屬性不存在時返回的默認值。

設置屬性

PropertyBs.getInstance("writeAndFlush.properties").setAndFlush("hello", "world-set");

writeAndFlush.properties 為文件路徑,hello 為需要設置的屬性信息。

引導類方法概覽

序號 方法 說明
1 getInstance(propertyPath) 獲取指定屬性文件路徑的引導類實例
2 charset(charset) 指定文件編碼,默認為 UTF-8
3 get(key) 獲取 key 對應的屬性值
4 getOrDefault(key, defaultValue) 獲取 key 對應的屬性值,不存在則返回 defaultValue
5 set(key, value) 設置值(內存)
6 remove(key) 移除值(內存)
7 flush() 刷新內存變更到當前文件磁盤
9 flush(path) 刷新內存變更到指定文件磁盤
10 set(map) 設置 map 信息到內存
11 set(bean) 設置 bean 對象信息到內存
12 asMap() 返回內存中屬性信息,作為 Map 返回
13 asBean(bean) 返回內存中屬性信息到 bean 對象中

對象

簡介

我們希望操作 property 可以想操作對象一樣符合 OO 的思想。

設置值

User user = new User();
user.setName("hello");
user.setHobby("hobby");

final long time = 1574147668411L;
user.setBirthday(new Date(time));

PropertyBs propertyBs = PropertyBs.getInstance("setBean.properties")
        .set(user);

Assert.assertEquals("hobby", propertyBs.get("myHobby"));
Assert.assertEquals("1574147668411", propertyBs.get("birthday"));

讀取值

PropertyBs propertyBs = PropertyBs.getInstance("setBean.properties"
        .set("myHobby", "play")
        .set("birthday", "1574147668411");
User user = new User();
propertyBs.asBean(user);
Assert.assertEquals("play", user.getHobby());
Assert.assertEquals(1574147668411L, user.getBirthday().getTime());

對象定義

  • User.java
public class User {

    private String name;

    @PropertyField("myHobby")
    private String hobby;

    @PropertyField(converter = DateValueConverter.class)
    private Date birthday;

}

@PropertyField 註解

序號 屬性 默認值 說明
1 value 當前字段名稱 對應的 property 屬性名稱
2 converter 默認轉換實現 DefaultValueConverter 對當前字段進行屬性的轉換處理

自定義轉換類

  • DateValueConverter.java

這個就是我們針對 Date 類型,自己實現的處理類型。

實現如下:

public class DateValueConverter implements IValueConverter {

    @Override
    public Object fieldValue(String value, IFieldValueContext context) {
        return new Date(Long.parseLong(value));
    }

    @Override
    public String propertyValue(Object value, IPropertyValueContext context) {
        Date date = (Date)value;
        return date.getTime()+"";
    }

}

集合

說明

有時候一個屬性可能是集合或者數組,這裏暫時給出比較簡單的實現。

將字段值直接根據逗號分隔,作為屬性值。

測試案例

UserArrayCollection userArrayCollection = buildUser();
PropertyBs propertyBs = PropertyBs.getInstance("setBeanArrayCollection.properties")
        .set(userArrayCollection);
Assert.assertEquals("array,collection", propertyBs.get("alias"));
Assert.assertEquals("array,collection", propertyBs.get("hobbies"));

對象定義

  • UserArrayCollection.java
public class UserArrayCollection {

    private List<String> alias;

    private String[] hobbies;
}

暫時只支持 String 類型,不想做的過於複雜。

後期將考慮添加各種類型的支持。

多級對象

說明

有時候我們在一個對象中會引用其他對象,比如 對象 a 中包含對象 b。

這裏採用 a.b.c 這種方式作為屬性的 key, 更加符合使用的習慣。

測試案例

設置

Book book = new Book();
book.name("《海底兩萬里》").price("12.34");
UserEntry user = new UserEntry();
user.name("海倫").book(book).age("10");
PropertyBs propertyBs = PropertyBs.getInstance("setBeanEntry.properties")
        .set(user);
Assert.assertEquals("海倫", propertyBs.get("name"));
Assert.assertEquals("10", propertyBs.get("age"));
Assert.assertEquals("《海底兩萬里》", propertyBs.get("book.name"));
Assert.assertEquals("12.34", propertyBs.get("book.price"));

讀取

Map<String, String> map = new HashMap<>();
map.put("name", "海倫");
map.put("age", "10");
map.put("book.name", "《海底兩萬里》");
map.put("book.price", "12.34");
UserEntry userEntry = new UserEntry();
PropertyBs.getInstance("setBeanEntry.properties")
        .set(map)
        .asBean(userEntry);
Assert.assertEquals("UserEntry{name='海倫', age=10, book=Book{name='《海底兩萬里》', price=12.34}}",
        userEntry.toString());

對象定義

  • UserEntry.java
public class UserEntry {

    private String name;

    private String age;

    @PropertyEntry
    private Book book;

}
  • Book.java
public class Book {

    private String name;

    private String price;

}

@PropertyEntry 說明

@PropertyEntry 註解用來標識一個字段是否採用多級對象的方式表示。

這個註解只有一個屬性,就是 value(),可以用來給當前字段指定一個別稱,和 @PropertyField 別稱類似。

後續特性

  • 提供更多內置的類型轉換實現

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

【其他文章推薦】

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

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

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

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

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

※試算大陸海運運費!

Mysql備份還有這麼多套路,還不了解下?

邏輯備份和物理備份

邏輯備份

邏輯備份用於備份數據庫的結構(CREAET DATABASE、CREATE TABLE)和數據(INSERT),這種備份類型適合數據量小、跨SQL服務器、需要修改數據等場景。如mysqldump命令就是產生一個邏輯備份工具,使用mysqldump輸出的文件包含CREATE TABLEINSERT語句,能夠直接重建表內容和表結構。

使用邏輯備份有以下優勢和劣勢:

優勢
  • 可移植性高,SQL語句可直接適用於其他SQL服務器;
  • 在數據恢復之前可增加、修改數據;
  • 數據恢復粒度小可以是服務器、數據庫、表級別;
  • 使用文本格式,可讀性高;
劣勢
  • 備份時需要訪問mysql服務器,影響其他客戶端;
  • 需要將數據轉換成邏輯格式(SQL,CSV);
  • 如果命令運行在客戶端,mysql服務器還需要將數據發送給客戶端;
  • 因為輸出格式為文本文件,佔用空間較大;

物理備份

物理備份是包括存儲數據庫內容的目錄和文件的副本,這種類型的備份適用於需要在出現問題時快速恢復的大型重要數據庫。

優勢
  • 完整的Mysql文件和目錄備份,只需要複製文件不需要轉換,速度比邏輯備份更快;
  • 除了備份數據,還能備份配置文件和日誌文件;
  • 不需要運行Mysql服務器就可以完成備份;
  • 備份工具簡單使用cp、scp、tar命令即可完成備份;
劣勢
  • 可移植性不高,恢複數據只適用於相同或類似的機器上;
  • 為了保持數據庫文件的一致性,需要停機備份;
  • 恢復粒度不能按表或用戶恢復;

在線備份和離線備份

在線備份需要mysql服務器處理運行狀態,以便備份工具從mysql服務器中獲取數據。離線備份表示mysql服務器處理停止狀態。兩種備份形式也可以稱為“熱備份”和“冷備份“。

在線備份的主要特性

  • 備份不需要停機,對其他客戶端影響較小其他連接能夠正常訪問mysql服務器(依賴操作類型,如讀操作);
  • 備份需要加鎖,以免在備份期間對數據做出修改;

離線備份的主要特性

  • 備份期間服務器不可用;
  • 備份過程更簡單,不會受到客戶端的干擾;

邏輯備份(mysqldump使用)

mysqldump屬於邏輯備份命令,使用mysqldump備份的優勢是它非常方便和靈活,可以直接編輯輸出文件或者使用導入到其他的SQL服務器中去,但是它不能用作備份大量數據的快速解決方案,對於大數據量,即使備份花費的時候可以接受,但是恢複數據也可能會非常緩慢,因為執執行SQL語句會涉及磁盤I/O進行插入,創建索引等。mysqldump的使用方式非常簡單:

shell> mysqldump [options] db_name [tbl_name ...]
shell> mysqldump [options] --databases db_name ...
shell> mysqldump [options] --all-databases

使用mysqldump備份時要注意:數據庫的一致狀態,在執行mysqldump命令時要保證數據不會再發生變更,保持數據的一致性有二種方法:

  • 使Mysql服務器只讀
  • 使用事務加上隔離級別:REPEATABLE READ

使用REPEATABLE READ事務隔離級別執行mysqldump命令(使用事務保持數據庫的一致狀態):

mysqldump --master-data=2 \
 --flush-logs  \
 --single-transaction  \
 --all-databases > /backup/`date +%F-%H`-mysql-all.sql  

備份參數說明:

  • –master-data: 將二進制日誌文件的名稱和位置備份
  • –flush-logs: 開始備份之前刷新mysql服務器日誌文件
  • –single-transaction:開始備份之前設置事務隔離級別為REPEATABLE READ然後發送一個START TRANSACTION命令。
  • –all-databases:備份所有數據庫

物理備份(複製原始文件)

為了保證複製文件的完整性,備份原始文件最好是停止mysql服務器,複製原始文件備份由以下步驟完成:

  1. 停止mysql服務器
    $ mysqladmin shutdown
  2. 使用合適的工具複製原始數據文件
    $ tar cf /tmp/dbbackup.tar ./data
  3. 備份完成后,運行mysql服務器
    $ mysqld_safe

使用主從備份模式

使用mysqldumptar備份或多或少都會對業務產生影響,使用mysqldump備份需要對數據加鎖,加鎖就意味着其他客戶端操作受到限制。使用tar命令需要停止服務器直接導致數據庫服務器不可用,有沒有辦法能解決這兩種問題呢?答案是有的,就是使用主從備份模式。

在單機的基礎上增加一台Slave機器對Master機器的數據進行同步:

開始備份時對Slave進行備份,這樣即使Slave停機或對數據加鎖也不會影響業務的正常使用,如果公司有條件或業務非常重要可以選擇這種方案來備份數據。

歡迎關注微信公眾號《架構文摘》,高質量技術文章第一時間推送。

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

【其他文章推薦】

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

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

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

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

※專營大陸快遞台灣服務

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

asp.net core 自定義 Policy 替換 AllowAnonymous 的行為

asp.net core 自定義 Policy 替換 AllowAnonymous 的行為

Intro

最近對我們的服務進行了改造,原本內部服務在內部可以匿名調用,現在增加了限制,通過 identity server 來管理 api 和 client,網關和需要訪問api的客戶端或api服務相互調用通過 client_credencial 的方式來調用,這樣一來我們可以清晰知道哪些 api 服務會被哪些 api/client 所調用,而且安全性來說更好。
為了保持後端服務的代碼更好的兼容性,希望能夠實現相同的代碼通過在 Startup 里不同的配置實現不同的 Authorization 邏輯,原來我們的服務的 Authorize 都是以 Authorize("policyName") 的形式來寫的,這樣一來我們只需要修改這個 Policy 的授權配置就可以了。對於 AllowAnonymous 就希望可以通過一種類似的方式來實現,通過自定義一個 Policy 來實現自己的邏輯

實現方式

將 action 上的 AllowAnonymous 替換為 Authorize("policyName"),在沒有設置 Authorize 的 controller 上增加 Authorize("policyName")

public class AllowAnonymousPolicyTransformer : IApplicationModelConvention
{
    private readonly string _policyName;

    public AllowAnonymousPolicyTransformer() : this("anonymous")
    {
    }

    public AllowAnonymousPolicyTransformer(string policyName) => _policyName = policyName;

    public void Apply(ApplicationModel application)
    {
        foreach (var controllerModel in application.Controllers)
        {
            if (controllerModel.Filters.Any(_ => _.GetType() == typeof(AuthorizeFilter)))
            {
                foreach (var actionModel in controllerModel.Actions)
                {
                    if (actionModel.Filters.Any(_ => _.GetType() == typeof(AllowAnonymousFilter)))
                    {
                        var allowAnonymousFilter = actionModel.Filters.First(_ => _.GetType() == typeof(AllowAnonymousFilter));
                        actionModel.Filters.Remove(allowAnonymousFilter);
                        actionModel.Filters.Add(new AuthorizeFilter(_policyName));
                    }
                }
            }
            else
            {
                if (controllerModel.Filters.Any(_ => _.GetType() == typeof(AllowAnonymousFilter)))
                {
                    var allowAnonymousFilter = controllerModel.Filters.First(_ => _.GetType() == typeof(AllowAnonymousFilter));
                    controllerModel.Filters.Remove(allowAnonymousFilter);
                }
                controllerModel.Filters.Add(new AuthorizeFilter(_policyName));
            }
        }
    }
}

public static class MvcBuilderExtensions
{
    public static IMvcBuilder AddAnonymousPolicyTransformer(this IMvcBuilder builder)
    {
        builder.Services.Configure<MvcOptions>(options =>
        {
            options.Conventions.Insert(0, new AllowAnonymousPolicyTransformer());
        });
        return builder;
    }

    public static IMvcBuilder AddAnonymousPolicyTransformer(this IMvcBuilder builder, string policyName)
    {
        builder.Services.Configure<MvcOptions>(options =>
        {
            options.Conventions.Insert(0, new AllowAnonymousPolicyTransformer(policyName));
        });
        return builder;
    }
}

controller 中的代碼:

[Route("api/[controller]")]
public class ValuesController : Controller
{
    private readonly ILogger _logger;

    public ValuesController(ILogger<ValuesController> logger)
    {
        _logger = logger;
    }

    // GET api/values
    [HttpGet]
    public ActionResult<IEnumerable<string>> Get()
    {
        var msg = $"IsAuthenticated: {User.Identity.IsAuthenticated} ,UserName: {User.Identity.Name}";
        _logger.LogInformation(msg);
        return new string[] { msg };
    }

    // GET api/values/5
    [Authorize]
    [HttpGet("{id:int}")]
    public ActionResult<string> Get(int id)
    {
        return "value";
    }
    // ...
}

Startup 中 ConfigureServices 配置:

var anonymousPolicyName = "anonymous";

services.AddAuthorization(options =>
{
    options.AddPolicy(anonymousPolicyName, builder => builder.RequireAssertion(context => context.User.Identity.IsAuthenticated));

    options.DefaultPolicy = new AuthorizationPolicyBuilder(HeaderAuthenticationDefaults.AuthenticationSchema)
        .RequireAuthenticatedUser()
        .RequireAssertion(context => context.User.GetUserId<int>() > 0)
        .Build();
});

services.AddMvc(options =>
    {
        options.Conventions.Add(new ApiControllerVersionConvention());
    })
    .AddAnonymousPolicyTransformer(anonymousPolicyName)
    ;

實現效果

訪問原來的匿名接口

userId 為0訪問原來的匿名接口

userId 大於0訪問原來的匿名接口

userId 為0訪問需要登錄的接口

userId 大於0訪問需要登錄的接口

More

注:按照上面的做法已經可以做到自定義 policy 代替 AllowAnonymous 的行為,但是原來返回的401,現在可能返回到就是 403 了

Reference

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

【其他文章推薦】

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

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

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

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

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

併發編程-硬件加持的CAS操作夠快么?

Talk is cheap

CAS(Compare And Swap),即比較並交換。是解決多線程并行情況下使用鎖造成性能損耗的一種機制,CAS操作包含三個操作數——內存位置(V)、預期原值(A)和新值(B)。如果內存位置的值與預期原值相匹配,那麼處理器會自動將該位置值更新為新值。否則,處理器不做任何操作。無論位置V的值是否等於A, 都將返回V原有的值。

CAS的含義是”我認為V的值應該是A,如果是,那我將V的值更新為B,否則不修改並告訴V的值實際是多少“

Show you my code

在單線程環境中分別使用無鎖,加鎖以及cas進行十組5億次累加運算,然後打印出平均耗時。

 /**
 * cas對比加鎖測試
 *
 * @author Jann Lee
 * @date 2019-11-21 0:12
 **/
public class CasTest {

    @Test
    public void test() {
        long times = 500_000_000;
        // 記錄耗時
        List<Long> elapsedTime4NoLock = new ArrayList<>(10);
        List<Long> elapsedTime4Synchronized = new ArrayList<>(10);
        List<Long> elapsedTime4ReentrantLock = new ArrayList<>(10);
        List<Long> elapsedTime4Cas = new ArrayList<>(10);

        // 進行10組試驗
        for (int j = 0; j < 10; j++) {
            // 無鎖
            long startTime = System.currentTimeMillis();
            for (long i = 0; i < times; i++) {
            }
            long endTime = System.currentTimeMillis();
            elapsedTime4NoLock.add(endTime - startTime);

            // synchronized 關鍵字(隱式鎖)
            startTime = endTime;
            for (long i = 0; i < times; ) {
                i = addWithSynchronized(i);
            }
            endTime = System.currentTimeMillis();
            elapsedTime4Synchronized.add(endTime - startTime);

            // ReentrantLock 顯式鎖
            startTime = endTime;
            ReentrantLock lock = new ReentrantLock();
            for (long i = 0; i < times; ) {
                i = addWithReentrantLock(i, lock);
            }
            endTime = System.currentTimeMillis();
            elapsedTime4ReentrantLock.add(endTime - startTime);

            // cas(AtomicLong底層是用cas實現)
            startTime = endTime;
            AtomicLong atomicLong = new AtomicLong();
            while (atomicLong.getAndIncrement() < times) {
            }
            endTime = System.currentTimeMillis();
            elapsedTime4Cas.add(endTime - startTime);
        }

        System.out.println("無鎖計算耗時: " + average(elapsedTime4NoLock) + "ms");
        System.out.println("synchronized計算耗時: " + average(elapsedTime4Synchronized) + "ms");
        System.out.println("ReentrantLock計算耗時: " + average(elapsedTime4ReentrantLock) + "ms");
        System.out.println("cas計算耗時: " + average(elapsedTime4Cas) + "ms");

    }

    /**
     * synchronized加鎖
     */
    private synchronized long addWithSynchronized(long i) {
        i = i + 1;
        return i;
    }

    /**
     * ReentrantLock加鎖
     */
    private long addWithReentrantLock(long i, Lock lock) {
        lock.lock();
        i = i + 1;
        lock.unlock();
        return i;
    }

    /**
     * 計算平均耗時
     */
    private double average(Collection<Long> collection) {
        return collection.stream().mapToLong(i -> i).average().orElse(0);
    }
}

從案例中我們可能看出在單線程環境場景下cas的性能要高於鎖相關的操作。當然,在競爭比較激烈的情況下性能可能會有所下降,因為要不斷的重試和回退或者放棄操作,這也是CAS的一個缺點所在,因為這些重試,回退等操作通常用開發者來實現。

CAS的實現並非是簡單的代碼層面控制的,而是需要硬件的支持,因此在不同的體系架構之間執行的性能差異很大。但是一個很管用的經驗法則是:在大多數處理器上,在無競爭的鎖獲取和釋放的”快速代碼路徑“上的開銷,大約是CAS開銷的兩倍。

為何CAS如此優秀

硬件加持,現代大多數處理器都從硬件層面通過一些列指令實現CompareAndSwap(比較並交換)同步原語,進而使操作系統和JVM可以直接使用這些指令實現鎖和併發的數據結構。我們可以簡單認為,CAS是將比較和交換合成是一個原子操作

JVM對CAS的支持, 由於Java程序運行在JVM上,所以應對不同的硬件體系架構的處理則需要JVM來實現。在不支持CAS操作的硬件上,jvm將使用自旋鎖來實現。

CAS的ABA問題

cas操作讓我們減少了鎖帶來的性能損耗,同時也給我們帶來了新的麻煩-ABA問題。

在線程A讀取到x的值與執行CAS操作期間,線程B對x執行了兩次修改,x的值從100變成200,然後再從200變回100;而後在線程A執行CAS操作過程中並未發現x發生過變化,成功修改了x的值。由於x的值100 ->200->100,所以稱之為ABA的原因。

魔高一尺道高一丈,解決ABA的問題目前最常用的辦法就是給數據加上“版本號”,每次修改數據時同時改變版本號即可。

Q&A

在競爭比較激烈的情況下,CAS要進行回退,重試等操作才能得到正確的結果,那麼CAS一定比加鎖性能要高嗎?

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

【其他文章推薦】

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

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

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

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

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

jwt 實踐應用以及特殊案例思考

JSON Web Token 是 出的一份標準,使用 JSON 來傳遞數據,用於判定用戶是否登錄狀態。

jwt 之前,使用 session 來做用戶認證。

以下代碼均使用 javascript 編寫。

  • 原文鏈接:

session

傳統判斷是否登錄的方式是使用 session + token

token 是指在客戶端使用 token 作為用戶狀態憑證,瀏覽器一般存儲在 localStorage 或者 cookie 中。

session 是指在服務器端使用 redis 或者 sql 類數據庫,存儲 user_id 以及 token 的鍵值對關係,基本工作原理如下。

在服務器端使用 sessions 存儲鍵值對

const sessions = {
  "ABCED1": 10086,
  "CDEFA0": 10010
}

每次客戶端請求帶權限數據時攜帶 token,在服務器端根據 token 與 sessions 獲取 user_id, 完成認證過程

function getUserIdByToken (token) {
  return sessions[token]
}

如果存儲在 cookie 中就是經常聽到的 session + cookie 的登錄方案。其實存儲在 cookielocalStorage 甚至 IndexedDB 或者 WebSQL 各有利弊,核心思想一致。

關於 cookie 以及 token 優缺點,在 中有討論。

如果不使用 cookie,可以採取 localStorage + Authorization 的方式進行認證,更加無狀態化

// http 的頭,每次請求權限接口時,需要攜帶 Authorization Header
const headers = {
  Authorization: `Bearer ${localStorage.get('token')}`
}

推薦一個前端的存儲庫 ,使用 IndexedDBWebSQL 以及 IndexedDB 做鍵值對存儲。

無狀態登錄

session 需要在數據庫中保持用戶及token對應信息,所以叫 有狀態

試想一下,如何在數據庫中不保持用戶狀態也可以登錄。

第一種方法: 前端直接傳 user_id 給服務端

缺點也特別特別明顯,容易被用戶篡改成任意 user_id,權限設置形同虛設。不過思路正確,接着往下走。

改進: 對 user_id 進行對稱加密

服務端對 user_id 進行對稱加密后,作為 token 返回客戶端,作為用戶狀態憑證。比上邊略微強點,但由於對稱加密,選擇合適的算法以及密鑰比較重要

改進: 對 user_id 不需要加密,只需要進行簽名,保證不被篡改

這便是 jwt 的思想:user_id,加密算法和簽名組成 token 一起存儲到客戶端,每當客戶端請求接口時攜帶 token,服務器根據 token 解析出加密算法與 user_id 來判斷簽名是否一致。

Json Web Token

jwt 根據 HeaderPayload 以及 Signature 三個部分由 . 拼接而成。

Header

Header 由非對稱加密算法和類型組成,如下

const header = {
  // 加密算法
  alg: 'HS256',
  type: 'jwt'
}

Payload

Payload 中由 以及需要通信的數據組成。這些數據字段也叫 Claim

Registered Claim 中比較重要的是 "exp" Claim 表示過期時間,在用戶登錄時會設置過期時間。

const payload = {
  // 表示 jwt 創建時間
  iat: 1532135735,

  // 表示 jwt 過期時間
  exp: 1532136735,

  // 用戶 id,用以通信
  user_id: 10086
}

Signature

SignatureHeaderPayload 以及 secretOrPrivateKey 計算而成。secretOrPrivateKey 作為敏感數據存儲在服務器端,可以考慮使用 vault secret 或者 k8s secret

對於 secretOrPrivateKey,如果加密算法採用 HMAC,則為字符串,如果採用 RSA 或者 ECDSA,則為 PrivateKey。

// 由 HMACSHA256 算法進行簽名,secret 不能外泄
const sign = HMACSHA256(base64.encode(header) + '.' + base64.encode(payload), secret)

// jwt 由三部分拼接而成
const jwt = base64.encode(header) + '.' + base64.encode(payload) + '.' + sign

從生成 jwt 規則可知客戶端可以解析出 payload,因此不要在 payload 中攜帶敏感數據,比如用戶密碼

校驗過程

在生成規則中可知,jwt 前兩部分是對 header 以及 payload 的 base64 編碼。

當服務器收到客戶端的 token 后,解析前兩部分得到 header 以及 payload,並使用 header 中的算法與 secretOrPrivateKey 進行簽名,判斷與 jwt 中攜帶的簽名是否一致。

帶個問題,如何判斷 token 過期?

應用

由上可知,jwt 並不對數據進行加密,而是對數據進行簽名,保證不被篡改。除了在登錄中可以用到,在進行郵箱校驗,圖形驗證碼和短信驗證碼時也可以用到。

圖形驗證碼

在登錄時,輸入密碼錯誤次數過多會出現圖形驗證碼。

圖形驗證碼的原理是給客戶端一個圖形,並且在服務器端保存與這個圖片配對的字符串,以前也大都通過 session 來實現。

可以把驗證碼配對的字符串作為 secret,進行無狀態校驗。

const jwt = require('jsonwebtoken')

// 假設驗證碼為字符驗證碼,字符為 ACDE,10分鐘失效
const token = jwt.sign({}, secrect + 'ACDE', { expiresIn: 60 * 10 })

const codeImage = getImageFromString('ACDE')

// 給前端的響應
const res = {
  // 驗證碼圖片的 token,從中可以校驗前端發送的驗證碼
  token,
  // 驗證碼圖片
  codeImage,
}

短信驗證碼與圖形驗證碼同理

郵箱校驗

現在網站在註冊成功後會進行郵箱校驗,具體做法是給郵箱發一個鏈接,用戶點開鏈接校驗成功。

// 把郵箱以及用戶id綁定在一起
const code = jwt.sign({ email, userId }, secret, { expiresIn: 60 * 30 })

// 在此鏈接校驗驗證碼
const link = `https://example.com/code=${code}`

無狀態 VS 有狀態

關於無狀態和有狀態,在其它技術方向也有對比,比如 React 的 stateLess component 以及 stateful component,函數式編程中的副作用可以理解為狀態,http 也是一個無狀態協議,需要靠 header 以及 cookie 攜帶狀態。

在用戶認證這裏,有無狀態是指是否依賴外部數據存儲,如 mysql,redis 等。

案例

思考以下幾個關於登錄的問題如何使用 session 以及 jwt 實現,來更加清楚 jwt 的使用場景

當用戶註銷時,如何使該 token 失效

因為 jwt 無狀態,不保存用戶設備信息,沒法單純使用它完成以上問題,可以再利用數據庫保存一些狀態完成。

  • session: 只需要把 user_id 對應的 token 清掉即可
  • jwt: 使用 redis,維護一張黑名單,用戶註銷時把該 token 加入黑名單,過期時間與 jwt 的過期時間保持一致。

如何允許用戶只能在一個設備登錄,如微信

  • session: 使用 sql 類數據庫,對用戶數據庫表添加 token 字段並加索引,每次登陸重置 token 字段,每次請求需要權限接口時,根據 token 查找 user_id
  • jwt: 假使使用 sql 類數據庫,對用戶數據庫表添加 token 字段(不需要添加索引),每次登陸重置 token 字段,每次請求需要權限接口時,根據 jwt 獲取 user_id,根據 user_id 查用戶表獲取 token 判斷 token 是否一致。另外也可以使用計數器的方法,如下一個問題。

對於這個需求,session 稍微簡單些,畢竟 jwt 也需要依賴數據庫。

如何允許用戶只能在最近五個設備登錄,如諸多播放器

  • session: 使用 sql 類數據庫,創建 token 數據庫表,有 id, token, user_id 三個字段,user 與 token 表為 1:m 關係。每次登錄添加一行記錄。根據 token 獲取 user_id,再根據 user_id 獲取該用戶有多少設備登錄,超過 5 個,則刪除最小 id 一行。
  • jwt: 使用計數器,使用 sql 類數據庫,在用戶表中添加字段 count,默認值為 0,每次登錄 count 字段自增1,每次登錄創建的 jwt 的 Payload 中攜帶數據 current_count 為用戶的 count 值。每次請求權限接口時,根據 jwt 獲取 count 以及 current_count,根據 user_id 查用戶表獲取 count,判斷與 current_count 差值是否小於 5

對於這個需求,jwt 略簡單些,而使用 session 還需要多維護一張 token 表。

如何允許用戶只能在最近五個設備登錄,而且使某一用戶踢掉除現有設備外的其它所有設備,如諸多播放器

  • session: 在上一個問題的基礎上,刪掉該設備以外其它所有的token記錄。
  • jwt: 在上一個問題的基礎上,對 count + 5,並對該設備重新賦值為新的 count。

如何显示該用戶登錄設備列表 / 如何踢掉特定用戶

  • session: 在 token 表中新加列 device
  • jwt: 需要服務器端保持設備列表信息,做法與 session 一樣,使用 jwt 意義不大

總結

從以上問題得知,如果不需要控制登錄設備數量以及設備信息,無狀態的 jwt 是一個不錯的選擇。一旦涉及到了設備信息,就需要對 jwt 添加額外的狀態支持,增加了認證的複雜度,此時選用 session 是一個不錯的選擇。

jwt 不是萬能的,是否採用 jwt,需要根據業務需求來確定。

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

【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

小三通物流營運型態?

※快速運回,大陸空運推薦?